• 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.DISABLED_AUTHENTICATION_FAILURE;
20 import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED;
21 import static android.net.wifi.WifiInfo.SECURITY_TYPE_EAP;
22 import static android.net.wifi.WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE;
23 import static android.net.wifi.WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT;
24 import static android.net.wifi.WifiInfo.SECURITY_TYPE_OPEN;
25 import static android.net.wifi.WifiInfo.SECURITY_TYPE_OWE;
26 import static android.net.wifi.WifiInfo.SECURITY_TYPE_PSK;
27 import static android.net.wifi.WifiInfo.SECURITY_TYPE_SAE;
28 import static android.net.wifi.WifiInfo.SECURITY_TYPE_WEP;
29 
30 import static com.android.wifitrackerlib.WifiEntry.CertificateInfo.CERTIFICATE_VALIDATION_METHOD_USING_CERTIFICATE_PINNING;
31 import static com.android.wifitrackerlib.WifiEntry.CertificateInfo.CERTIFICATE_VALIDATION_METHOD_USING_INSTALLED_ROOTCA;
32 import static com.android.wifitrackerlib.WifiEntry.CertificateInfo.CERTIFICATE_VALIDATION_METHOD_USING_SYSTEM_CERTIFICATE;
33 
34 import static java.util.Comparator.comparingInt;
35 import static java.util.stream.Collectors.toList;
36 
37 import android.app.admin.DevicePolicyManager;
38 import android.content.ComponentName;
39 import android.content.Context;
40 import android.content.pm.ApplicationInfo;
41 import android.content.pm.PackageManager;
42 import android.net.ConnectivityDiagnosticsManager;
43 import android.net.NetworkCapabilities;
44 import android.net.NetworkInfo;
45 import android.net.NetworkInfo.DetailedState;
46 import android.net.TransportInfo;
47 import android.net.wifi.MloLink;
48 import android.net.wifi.ScanResult;
49 import android.net.wifi.WifiConfiguration;
50 import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
51 import android.net.wifi.WifiEnterpriseConfig;
52 import android.net.wifi.WifiInfo;
53 import android.net.wifi.WifiScanner;
54 import android.os.Build;
55 import android.os.PersistableBundle;
56 import android.os.UserHandle;
57 import android.telephony.CarrierConfigManager;
58 import android.telephony.SubscriptionInfo;
59 import android.telephony.SubscriptionManager;
60 import android.telephony.TelephonyManager;
61 import android.text.TextUtils;
62 import android.text.format.DateUtils;
63 import android.util.Pair;
64 
65 import androidx.annotation.NonNull;
66 import androidx.annotation.Nullable;
67 import androidx.annotation.RequiresApi;
68 import androidx.core.os.BuildCompat;
69 
70 import java.net.InetAddress;
71 import java.net.UnknownHostException;
72 import java.util.ArrayList;
73 import java.util.Arrays;
74 import java.util.Collections;
75 import java.util.List;
76 import java.util.StringJoiner;
77 
78 /**
79  * Utility methods for WifiTrackerLib.
80  */
81 public class Utils {
82     // TODO(b/242144920): remove this after publishing this reason in U.
83     // This reason is added in U and hidden in T, using a hard-coded value first.
84     public static final int DISABLED_TRANSITION_DISABLE_INDICATION = 13;
85 
86     // Returns the ScanResult with the best RSSI from a list of ScanResults.
87     @Nullable
getBestScanResultByLevel(@onNull List<ScanResult> scanResults)88     public static ScanResult getBestScanResultByLevel(@NonNull List<ScanResult> scanResults) {
89         if (scanResults.isEmpty()) return null;
90 
91         return Collections.max(scanResults, comparingInt(scanResult -> scanResult.level));
92     }
93 
94     // Returns a list of WifiInfo SECURITY_TYPE_* supported by a ScanResult.
95     @NonNull
getSecurityTypesFromScanResult(@onNull ScanResult scanResult)96     public static List<Integer> getSecurityTypesFromScanResult(@NonNull ScanResult scanResult) {
97         List<Integer> securityTypes = new ArrayList<>();
98         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
99             for (int securityType : scanResult.getSecurityTypes()) {
100                 securityTypes.add(securityType);
101             }
102             return securityTypes;
103         }
104 
105         // Open network & its upgradable types
106         if (isScanResultForOweTransitionNetwork(scanResult)) {
107             securityTypes.add(WifiInfo.SECURITY_TYPE_OPEN);
108             securityTypes.add(WifiInfo.SECURITY_TYPE_OWE);
109             return securityTypes;
110         } else if (isScanResultForOweNetwork(scanResult)) {
111             securityTypes.add(WifiInfo.SECURITY_TYPE_OWE);
112             return securityTypes;
113         } else if (isScanResultForOpenNetwork(scanResult)) {
114             securityTypes.add(WifiInfo.SECURITY_TYPE_OPEN);
115             return securityTypes;
116         }
117 
118         // WEP network which has no upgradable type
119         if (isScanResultForWepNetwork(scanResult)) {
120             securityTypes.add(WifiInfo.SECURITY_TYPE_WEP);
121             return securityTypes;
122         }
123 
124         // WAPI PSK network which has no upgradable type
125         if (isScanResultForWapiPskNetwork(scanResult)) {
126             securityTypes.add(WifiInfo.SECURITY_TYPE_WAPI_PSK);
127             return securityTypes;
128         }
129 
130         // WAPI CERT network which has no upgradable type
131         if (isScanResultForWapiCertNetwork(scanResult)) {
132             securityTypes.add(
133                     WifiInfo.SECURITY_TYPE_WAPI_CERT);
134             return securityTypes;
135         }
136 
137         // WPA2 personal network & its upgradable types
138         if (isScanResultForPskNetwork(scanResult)
139                 && isScanResultForSaeNetwork(scanResult)) {
140             securityTypes.add(WifiInfo.SECURITY_TYPE_PSK);
141             securityTypes.add(WifiInfo.SECURITY_TYPE_SAE);
142             return securityTypes;
143         } else if (isScanResultForPskNetwork(scanResult)) {
144             securityTypes.add(WifiInfo.SECURITY_TYPE_PSK);
145             return securityTypes;
146         } else if (isScanResultForSaeNetwork(scanResult)) {
147             securityTypes.add(WifiInfo.SECURITY_TYPE_SAE);
148             return securityTypes;
149         }
150 
151         // WPA3 Enterprise 192-bit mode, WPA2/WPA3 enterprise network & its upgradable types
152         if (isScanResultForEapSuiteBNetwork(scanResult)) {
153             securityTypes.add(WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
154         } else if (isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)) {
155             securityTypes.add(WifiInfo.SECURITY_TYPE_EAP);
156             securityTypes.add(WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
157         } else if (isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)) {
158             securityTypes.add(WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
159         } else if (isScanResultForEapNetwork(scanResult)) {
160             securityTypes.add(WifiInfo.SECURITY_TYPE_EAP);
161         }
162         return securityTypes;
163     }
164 
165     // Returns a list of WifiInfo SECURITY_TYPE_* supported by a WifiConfiguration
166     // TODO(b/187755473): Use new public APIs to get the security type instead of relying on the
167     //                    legacy allowedKeyManagement bitset.
getSecurityTypesFromWifiConfiguration(@onNull WifiConfiguration config)168     static List<Integer> getSecurityTypesFromWifiConfiguration(@NonNull WifiConfiguration config) {
169         if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WAPI_CERT)) {
170             return Arrays.asList(WifiInfo.SECURITY_TYPE_WAPI_CERT);
171         } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WAPI_PSK)) {
172             return Arrays.asList(WifiInfo.SECURITY_TYPE_WAPI_PSK);
173         } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192)) {
174             return Arrays.asList(WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
175         } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
176             return Arrays.asList(WifiInfo.SECURITY_TYPE_OWE);
177         } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
178             return Arrays.asList(WifiInfo.SECURITY_TYPE_SAE);
179         } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA2_PSK)) {
180             return Arrays.asList(WifiInfo.SECURITY_TYPE_PSK);
181         } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)) {
182             if (config.requirePmf
183                     && !config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.TKIP)
184                     && config.allowedProtocols.get(WifiConfiguration.Protocol.RSN)) {
185                 return Arrays.asList(WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
186             } else {
187                 // WPA2 configs should also be valid for WPA3-Enterprise APs
188                 return Arrays.asList(
189                         WifiInfo.SECURITY_TYPE_EAP, WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
190             }
191         } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
192             return Arrays.asList(WifiInfo.SECURITY_TYPE_PSK);
193         } else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
194             if (config.wepKeys != null) {
195                 for (int i = 0; i < config.wepKeys.length; i++) {
196                     if (config.wepKeys[i] != null) {
197                         return Arrays.asList(WifiInfo.SECURITY_TYPE_WEP);
198                     }
199                 }
200             }
201         }
202         return Arrays.asList(WifiInfo.SECURITY_TYPE_OPEN);
203     }
204 
205     /**
206      * Returns a single WifiInfo security type from the list of multiple WifiInfo security
207      * types supported by an entry.
208      *
209      * Single security types will have a 1-to-1 mapping.
210      * Multiple security type networks will collapse to the lowest security type in the group:
211      *     - Open/OWE -> Open
212      *     - PSK/SAE -> PSK
213      *     - EAP/EAP-WPA3 -> EAP
214      */
getSingleSecurityTypeFromMultipleSecurityTypes( @onNull List<Integer> securityTypes)215     static int getSingleSecurityTypeFromMultipleSecurityTypes(
216             @NonNull List<Integer> securityTypes) {
217         if (securityTypes.size() == 0) {
218             return WifiInfo.SECURITY_TYPE_UNKNOWN;
219         }
220         if (securityTypes.size() == 1) {
221             return securityTypes.get(0);
222         }
223         if (securityTypes.size() == 2) {
224             if (securityTypes.contains(WifiInfo.SECURITY_TYPE_OPEN)) {
225                 return WifiInfo.SECURITY_TYPE_OPEN;
226             }
227             if (securityTypes.contains(WifiInfo.SECURITY_TYPE_PSK)) {
228                 return WifiInfo.SECURITY_TYPE_PSK;
229             }
230             if (securityTypes.contains(WifiInfo.SECURITY_TYPE_EAP)) {
231                 return WifiInfo.SECURITY_TYPE_EAP;
232             }
233         }
234         // Default to the first security type if we don't need any special mapping.
235         return securityTypes.get(0);
236     }
237 
238     /**
239      * Get the app label for a suggestion/specifier package name, or an empty String if none exist
240      */
getAppLabel(Context context, String packageName)241     static String getAppLabel(Context context, String packageName) {
242         try {
243             ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(
244                     packageName,
245                     0 /* flags */);
246             return appInfo.loadLabel(context.getPackageManager()).toString();
247         } catch (PackageManager.NameNotFoundException e) {
248             return "";
249         }
250     }
251 
getConnectedDescription(@onNull Context context, @Nullable WifiConfiguration wifiConfiguration, @NonNull NetworkCapabilities networkCapabilities, @Nullable WifiInfo wifiInfo, boolean isDefaultNetwork, boolean isLowQuality, @Nullable ConnectivityDiagnosticsManager.ConnectivityReport connectivityReport)252     static String getConnectedDescription(@NonNull Context context,
253             @Nullable WifiConfiguration wifiConfiguration,
254             @NonNull NetworkCapabilities networkCapabilities,
255             @Nullable WifiInfo wifiInfo,
256             boolean isDefaultNetwork,
257             boolean isLowQuality,
258             @Nullable ConnectivityDiagnosticsManager.ConnectivityReport connectivityReport) {
259         final StringJoiner sj = new StringJoiner(context.getString(
260                 R.string.wifitrackerlib_summary_separator));
261 
262         boolean isValidated = networkCapabilities.hasCapability(
263                 NetworkCapabilities.NET_CAPABILITY_VALIDATED);
264         boolean isCaptivePortal = networkCapabilities.hasCapability(
265                 NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL);
266         boolean isPartialConnectivity = networkCapabilities.hasCapability(
267                 NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY);
268         boolean isNoInternetExpected = wifiConfiguration != null
269                 && wifiConfiguration.isNoInternetAccessExpected();
270         boolean isPrivateDnsBroken = !isValidated && networkCapabilities.isPrivateDnsBroken();
271         boolean isCheckingForInternetAccess = !isValidated && !isPartialConnectivity
272                 && connectivityReport == null && !isNoInternetExpected;
273         boolean isOemNetwork = NonSdkApiWrapper.isOemCapabilities(networkCapabilities);
274         String suggestionOrSpecifierLabel = null;
275         if (wifiConfiguration != null
276                 && (wifiConfiguration.fromWifiNetworkSuggestion
277                 || wifiConfiguration.fromWifiNetworkSpecifier)) {
278             suggestionOrSpecifierLabel = getSuggestionOrSpecifierLabel(context, wifiConfiguration);
279         }
280         final boolean shouldShowConnected;
281         if (isValidated) {
282             shouldShowConnected = isDefaultNetwork;
283         } else {
284             // Show "Connected" even if we aren't validated specifically for the
285             // "Connected / No internet access" case, and for OEM-specified networks which aren't
286             // expected to be fully validated.
287             shouldShowConnected = !isCheckingForInternetAccess && !isCaptivePortal
288                     && !isPrivateDnsBroken && !isNoInternetExpected || isOemNetwork;
289         }
290 
291         if (!TextUtils.isEmpty(suggestionOrSpecifierLabel)) {
292             if (shouldShowConnected || (isDefaultNetwork && isPartialConnectivity)) {
293                 // "Connected via app"
294                 sj.add(context.getString(R.string.wifitrackerlib_connected_via_app,
295                         suggestionOrSpecifierLabel));
296             } else {
297                 // "Available via app"
298                 sj.add(context.getString(R.string.wifitrackerlib_available_via_app,
299                         suggestionOrSpecifierLabel));
300             }
301         } else if (shouldShowConnected) {
302             // "Connected"
303             sj.add(context.getResources().getStringArray(
304                     R.array.wifitrackerlib_wifi_status)[DetailedState.CONNECTED.ordinal()]);
305         }
306 
307         if (shouldShowConnected) {
308             if (wifiInfo != null && wifiInfo.getCurrentSecurityType() == SECURITY_TYPE_WEP) {
309                 // "WEP (less secure)"
310                 sj.add(context.getString(R.string.wifi_connected_less_secure,
311                         getSecurityString(context, Arrays.asList(SECURITY_TYPE_WEP), false)));
312             }
313         }
314 
315         if (isLowQuality) {
316             // "Low quality"
317             sj.add(context.getString(R.string.wifi_connected_low_quality));
318         }
319 
320         if (isCaptivePortal) {
321             // "Sign in to network"
322             sj.add(context.getString(context.getResources().getIdentifier(
323                     "network_available_sign_in", "string", "android")));
324         } else if (isPartialConnectivity) {
325             // "Limited connection..."
326             sj.add(context.getString(R.string.wifitrackerlib_wifi_limited_connection));
327         } else if (isCheckingForInternetAccess) {
328             // "Checking for internet access..."
329             sj.add(context.getString(R.string.wifitrackerlib_checking_for_internet_access));
330         } else if (isPrivateDnsBroken) {
331             // "Private DNS server cannot be accessed"
332             sj.add(context.getString(R.string.wifitrackerlib_private_dns_broken));
333         } else if (!isValidated) {
334             if (isNoInternetExpected) {
335                 // "Connected to device. Can't provide internet."
336                 sj.add(context.getString(
337                         R.string.wifitrackerlib_wifi_connected_cannot_provide_internet));
338             } else {
339                 // "No internet access"
340                 sj.add(context.getString(R.string.wifitrackerlib_wifi_no_internet));
341             }
342         }
343 
344         return sj.toString();
345     }
346 
getConnectingDescription(Context context, NetworkInfo networkInfo)347     static String getConnectingDescription(Context context, NetworkInfo networkInfo) {
348         if (context == null || networkInfo == null) {
349             return "";
350         }
351         DetailedState detailedState = networkInfo.getDetailedState();
352         if (detailedState == null) {
353             return "";
354         }
355 
356         final String[] wifiStatusArray = context.getResources()
357                 .getStringArray(R.array.wifitrackerlib_wifi_status);
358         final int index = detailedState.ordinal();
359         return index >= wifiStatusArray.length ? "" : wifiStatusArray[index];
360     }
361 
362 
getDisconnectedDescription( @onNull WifiTrackerInjector injector, Context context, WifiConfiguration wifiConfiguration, boolean forSavedNetworksPage, boolean concise)363     static String getDisconnectedDescription(
364             @NonNull WifiTrackerInjector injector,
365             Context context,
366             WifiConfiguration wifiConfiguration,
367             boolean forSavedNetworksPage,
368             boolean concise) {
369         if (context == null || wifiConfiguration == null) {
370             return "";
371         }
372         final StringJoiner sj = new StringJoiner(context.getString(
373                 R.string.wifitrackerlib_summary_separator));
374 
375         // For "Saved", "Saved by ...", and "Available via..."
376         if (concise) {
377             sj.add(context.getString(R.string.wifitrackerlib_wifi_disconnected));
378         } else if (forSavedNetworksPage && !wifiConfiguration.isPasspoint()) {
379             if (!injector.getNoAttributionAnnotationPackages().contains(
380                     wifiConfiguration.creatorName)) {
381                 final CharSequence appLabel = getAppLabel(context,
382                         wifiConfiguration.creatorName);
383                 if (!TextUtils.isEmpty(appLabel)) {
384                     sj.add(context.getString(R.string.wifitrackerlib_saved_network, appLabel));
385                 }
386             }
387         } else {
388             if (wifiConfiguration.fromWifiNetworkSuggestion) {
389                 final String suggestionOrSpecifierLabel =
390                         getSuggestionOrSpecifierLabel(context, wifiConfiguration);
391                 if (!TextUtils.isEmpty(suggestionOrSpecifierLabel)) {
392                     sj.add(context.getString(
393                             R.string.wifitrackerlib_available_via_app,
394                             suggestionOrSpecifierLabel));
395                 }
396             } else {
397                 sj.add(context.getString(R.string.wifitrackerlib_wifi_remembered));
398             }
399         }
400 
401         // For failure messages and disabled reasons
402         final String wifiConfigFailureMessage =
403                 getWifiConfigurationFailureMessage(context, wifiConfiguration);
404         if (!TextUtils.isEmpty(wifiConfigFailureMessage)) {
405             sj.add(wifiConfigFailureMessage);
406         }
407 
408         return sj.toString();
409     }
410 
getSuggestionOrSpecifierLabel( Context context, WifiConfiguration wifiConfiguration)411     private static String getSuggestionOrSpecifierLabel(
412             Context context, WifiConfiguration wifiConfiguration) {
413         if (context == null || wifiConfiguration == null) {
414             return "";
415         }
416 
417         final String carrierName = getCarrierNameForSubId(context,
418                 getSubIdForConfig(context, wifiConfiguration));
419         if (!TextUtils.isEmpty(carrierName)) {
420             return carrierName;
421         }
422         final String suggestorLabel = getAppLabel(context, wifiConfiguration.creatorName);
423         if (!TextUtils.isEmpty(suggestorLabel)) {
424             return suggestorLabel;
425         }
426         // Fall-back to the package name in case the app label is missing
427         return wifiConfiguration.creatorName;
428     }
429 
getWifiConfigurationFailureMessage( Context context, WifiConfiguration wifiConfiguration)430     private static String getWifiConfigurationFailureMessage(
431             Context context, WifiConfiguration wifiConfiguration) {
432         if (context == null || wifiConfiguration == null) {
433             return "";
434         }
435 
436         // Check for any failure messages to display
437         NetworkSelectionStatus networkSelectionStatus =
438                 wifiConfiguration.getNetworkSelectionStatus();
439         if (networkSelectionStatus.getNetworkSelectionStatus() != NETWORK_SELECTION_ENABLED) {
440             switch (networkSelectionStatus.getNetworkSelectionDisableReason()) {
441                 case NetworkSelectionStatus.DISABLED_CONSECUTIVE_FAILURES:
442                     if (!networkSelectionStatus.hasEverConnected()
443                             && networkSelectionStatus.getDisableReasonCounter(
444                                     NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE) > 0) {
445                         return context.getString(
446                                 R.string.wifitrackerlib_wifi_disabled_password_failure);
447                     }
448                     return context.getString(
449                             R.string.wifitrackerlib_wifi_disabled_consecutive_failures);
450                 case NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE:
451                 case NetworkSelectionStatus.DISABLED_AUTHENTICATION_NO_CREDENTIALS:
452                 case NetworkSelectionStatus.DISABLED_AUTHENTICATION_NO_SUBSCRIPTION:
453                     return context.getString(
454                             R.string.wifitrackerlib_wifi_disabled_password_failure);
455                 case NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD:
456                     return context.getString(R.string.wifitrackerlib_wifi_check_password_try_again);
457                 case NetworkSelectionStatus.DISABLED_DHCP_FAILURE:
458                     return context.getString(R.string.wifitrackerlib_wifi_disabled_network_failure);
459                 case NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION:
460                     return context.getString(R.string.wifitrackerlib_wifi_disabled_generic);
461                 case NetworkSelectionStatus.DISABLED_NO_INTERNET_PERMANENT:
462                 case NetworkSelectionStatus.DISABLED_NO_INTERNET_TEMPORARY:
463                     return context.getString(R.string.wifitrackerlib_wifi_no_internet);
464                 case DISABLED_TRANSITION_DISABLE_INDICATION:
465                     return context.getString(
466                             R.string.wifitrackerlib_wifi_disabled_transition_disable_indication);
467                 default:
468                     break;
469             }
470         } else { // NETWORK_SELECTION_ENABLED
471             // Show failure message if we've gotten AUTHENTICATION_FAILURE and have never connected
472             // before, which usually indicates the credentials are wrong.
473             if (networkSelectionStatus.getDisableReasonCounter(DISABLED_AUTHENTICATION_FAILURE) > 0
474                     && !networkSelectionStatus.hasEverConnected()) {
475                 return context.getString(R.string.wifitrackerlib_wifi_disabled_password_failure);
476             }
477             // Show DHCP failure message even if we're not disabled.
478             if (networkSelectionStatus.getDisableReasonCounter(
479                     NetworkSelectionStatus.DISABLED_DHCP_FAILURE) > 0) {
480                 return context.getString(R.string.wifitrackerlib_wifi_disabled_network_failure);
481             }
482         }
483         switch (wifiConfiguration.getRecentFailureReason()) {
484             case WifiConfiguration.RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA:
485             case WifiConfiguration.RECENT_FAILURE_REFUSED_TEMPORARILY:
486             case WifiConfiguration.RECENT_FAILURE_DISCONNECTION_AP_BUSY:
487                 return context.getString(R.string
488                         .wifitrackerlib_wifi_ap_unable_to_handle_new_sta);
489             case WifiConfiguration.RECENT_FAILURE_POOR_CHANNEL_CONDITIONS:
490                 return context.getString(R.string.wifitrackerlib_wifi_poor_channel_conditions);
491             case WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_UNSPECIFIED:
492             case WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_AIR_INTERFACE_OVERLOADED:
493             case WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_AUTH_SERVER_OVERLOADED:
494                 return context.getString(R.string
495                         .wifitrackerlib_wifi_mbo_assoc_disallowed_cannot_connect);
496             case WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_MAX_NUM_STA_ASSOCIATED:
497                 return context.getString(R.string
498                         .wifitrackerlib_wifi_mbo_assoc_disallowed_max_num_sta_associated);
499             case WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_INSUFFICIENT_RSSI:
500             case WifiConfiguration.RECENT_FAILURE_OCE_RSSI_BASED_ASSOCIATION_REJECTION:
501                 return context.getString(R.string
502                         .wifitrackerlib_wifi_mbo_oce_assoc_disallowed_insufficient_rssi);
503             case WifiConfiguration.RECENT_FAILURE_NETWORK_NOT_FOUND:
504                 return context.getString(R.string.wifitrackerlib_wifi_network_not_found);
505             default:
506                 // do nothing
507         }
508         return "";
509     }
510 
getAutoConnectDescription(@onNull Context context, @NonNull WifiEntry wifiEntry)511     static String getAutoConnectDescription(@NonNull Context context,
512             @NonNull WifiEntry wifiEntry) {
513         if (context == null || wifiEntry == null || !wifiEntry.canSetAutoJoinEnabled()) {
514             return "";
515         }
516 
517         return wifiEntry.isAutoJoinEnabled()
518                 ? "" : context.getString(R.string.wifitrackerlib_auto_connect_disable);
519     }
520 
getMeteredDescription(@onNull Context context, @Nullable WifiEntry wifiEntry)521     static String getMeteredDescription(@NonNull Context context, @Nullable WifiEntry wifiEntry) {
522         if (context == null || wifiEntry == null) {
523             return "";
524         }
525 
526         if (!wifiEntry.canSetMeteredChoice()
527                 && wifiEntry.getMeteredChoice() != WifiEntry.METERED_CHOICE_METERED) {
528             return "";
529         }
530 
531         if (wifiEntry.getMeteredChoice() == WifiEntry.METERED_CHOICE_METERED) {
532             return context.getString(R.string.wifitrackerlib_wifi_metered_label);
533         } else if (wifiEntry.getMeteredChoice() == WifiEntry.METERED_CHOICE_UNMETERED) {
534             return context.getString(R.string.wifitrackerlib_wifi_unmetered_label);
535         } else { // METERED_CHOICE_AUTO
536             return wifiEntry.isMetered() ? context.getString(
537                     R.string.wifitrackerlib_wifi_metered_label) : "";
538         }
539     }
540 
getVerboseSummary(@onNull WifiEntry wifiEntry)541     static String getVerboseSummary(@NonNull WifiEntry wifiEntry) {
542         if (wifiEntry == null) {
543             return "";
544         }
545 
546         final StringJoiner sj = new StringJoiner(" ");
547 
548         final String wifiInfoDescription = wifiEntry.getWifiInfoDescription();
549         if (!TextUtils.isEmpty(wifiInfoDescription)) {
550             sj.add(wifiInfoDescription);
551         }
552 
553         final String networkCapabilityDescription = wifiEntry.getNetworkCapabilityDescription();
554         if (!TextUtils.isEmpty(networkCapabilityDescription)) {
555             sj.add(networkCapabilityDescription);
556         }
557 
558         final String scanResultsDescription = wifiEntry.getScanResultDescription();
559         if (!TextUtils.isEmpty(scanResultsDescription)) {
560             sj.add(scanResultsDescription);
561         }
562 
563         final String networkSelectionDescription = wifiEntry.getNetworkSelectionDescription();
564         if (!TextUtils.isEmpty(networkSelectionDescription)) {
565             sj.add(networkSelectionDescription);
566         }
567 
568         return sj.toString();
569     }
570 
getNetworkSelectionDescription(WifiConfiguration wifiConfig)571     static String getNetworkSelectionDescription(WifiConfiguration wifiConfig) {
572         if (wifiConfig == null) {
573             return "";
574         }
575 
576         StringBuilder description = new StringBuilder();
577         NetworkSelectionStatus networkSelectionStatus = wifiConfig.getNetworkSelectionStatus();
578 
579         if (networkSelectionStatus.getNetworkSelectionStatus() != NETWORK_SELECTION_ENABLED) {
580             description.append(" (" + networkSelectionStatus.getNetworkStatusString());
581             if (networkSelectionStatus.getDisableTime() > 0) {
582                 long now = System.currentTimeMillis();
583                 long elapsedSeconds = (now - networkSelectionStatus.getDisableTime()) / 1000;
584                 description.append(" " + DateUtils.formatElapsedTime(elapsedSeconds));
585             }
586             description.append(")");
587         }
588 
589         int maxNetworkSelectionDisableReason =
590                 NetworkSelectionStatus.getMaxNetworkSelectionDisableReason();
591         for (int reason = 0; reason <= maxNetworkSelectionDisableReason; reason++) {
592             int disableReasonCounter = networkSelectionStatus.getDisableReasonCounter(reason);
593             if (disableReasonCounter == 0) {
594                 continue;
595             }
596             description.append(" ")
597                     .append(NetworkSelectionStatus.getNetworkSelectionDisableReasonString(reason))
598                     .append("=")
599                     .append(disableReasonCounter);
600         }
601         return description.toString();
602     }
603 
604     /**
605      * Returns the display string corresponding to the detailed state of the given NetworkInfo
606      */
getNetworkDetailedState(Context context, NetworkInfo networkInfo)607     static String getNetworkDetailedState(Context context, NetworkInfo networkInfo) {
608         if (context == null || networkInfo == null) {
609             return "";
610         }
611         DetailedState detailedState = networkInfo.getDetailedState();
612         if (detailedState == null) {
613             return "";
614         }
615 
616         String[] wifiStatusArray = context.getResources()
617                 .getStringArray(R.array.wifitrackerlib_wifi_status);
618         int index = detailedState.ordinal();
619         return index >= wifiStatusArray.length ? "" : wifiStatusArray[index];
620     }
621 
622     /**
623      * Check if the SIM is present for target carrier Id. If the carrierId is
624      * {@link TelephonyManager#UNKNOWN_CARRIER_ID}, then this returns true if there is any SIM
625      * present.
626      */
isSimPresent(@onNull Context context, int carrierId)627     static boolean isSimPresent(@NonNull Context context, int carrierId) {
628         SubscriptionManager subscriptionManager =
629                 (SubscriptionManager) context.getSystemService(
630                         Context.TELEPHONY_SUBSCRIPTION_SERVICE);
631         if (subscriptionManager == null) return false;
632         List<SubscriptionInfo> subInfoList = subscriptionManager.getActiveSubscriptionInfoList();
633         if (subInfoList == null || subInfoList.isEmpty()) {
634             return false;
635         }
636         if (carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
637             // Return true if any SIM is present for UNKNOWN_CARRIER_ID since the framework will
638             // match this to the default data SIM.
639             return true;
640         }
641         return subInfoList.stream()
642                 .anyMatch(info -> info.getCarrierId() == carrierId);
643     }
644 
645     /**
646      * Get the SIM carrier name for target subscription Id.
647      */
getCarrierNameForSubId(@onNull Context context, int subId)648     static @Nullable String getCarrierNameForSubId(@NonNull Context context, int subId) {
649         if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
650             return null;
651         }
652         TelephonyManager telephonyManager =
653                 (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
654         if (telephonyManager == null) return null;
655         TelephonyManager specifiedTm = telephonyManager.createForSubscriptionId(subId);
656         if (specifiedTm == null) {
657             return null;
658         }
659         return specifiedTm.getSimOperatorName();
660     }
661 
isServerCertUsedNetwork(@onNull WifiConfiguration config)662     static boolean isServerCertUsedNetwork(@NonNull WifiConfiguration config) {
663         return config.enterpriseConfig != null && config.enterpriseConfig
664                 .isEapMethodServerCertUsed();
665     }
isSimCredential(@onNull WifiConfiguration config)666     static boolean isSimCredential(@NonNull WifiConfiguration config) {
667         return config.enterpriseConfig != null
668                 && config.enterpriseConfig.isAuthenticationSimBased();
669     }
670 
671     /**
672      * Get the best match subscription Id for target WifiConfiguration.
673      */
getSubIdForConfig(@onNull Context context, @NonNull WifiConfiguration config)674     static int getSubIdForConfig(@NonNull Context context, @NonNull WifiConfiguration config) {
675         if (config.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
676             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
677         }
678         SubscriptionManager subscriptionManager =
679                 (SubscriptionManager) context.getSystemService(
680                         Context.TELEPHONY_SUBSCRIPTION_SERVICE);
681         if (subscriptionManager == null) {
682             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
683         }
684         List<SubscriptionInfo> subInfoList = subscriptionManager.getActiveSubscriptionInfoList();
685         if (subInfoList == null || subInfoList.isEmpty()) {
686             return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
687         }
688 
689         int matchSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
690         int dataSubId = SubscriptionManager.getDefaultDataSubscriptionId();
691         for (SubscriptionInfo subInfo : subInfoList) {
692             if (subInfo.getCarrierId() == config.carrierId) {
693                 matchSubId = subInfo.getSubscriptionId();
694                 if (matchSubId == dataSubId) {
695                     // Priority of Data sub is higher than non data sub.
696                     break;
697                 }
698             }
699         }
700         return matchSubId;
701     }
702 
703     /**
704      * Check if target subscription Id requires IMSI privacy protection.
705      */
isImsiPrivacyProtectionProvided(@onNull Context context, int subId)706     static boolean isImsiPrivacyProtectionProvided(@NonNull Context context, int subId) {
707         CarrierConfigManager carrierConfigManager =
708                 (CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
709         if (carrierConfigManager == null) {
710             return false;
711         }
712         PersistableBundle bundle = carrierConfigManager.getConfigForSubId(subId);
713         if (bundle == null) {
714             return false;
715         }
716         return (bundle.getInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT)
717                 & TelephonyManager.KEY_TYPE_WLAN) != 0;
718     }
719 
getImsiProtectionDescription(Context context, @Nullable WifiConfiguration wifiConfig)720     static CharSequence getImsiProtectionDescription(Context context,
721             @Nullable WifiConfiguration wifiConfig) {
722         if (context == null || wifiConfig == null || !isSimCredential(wifiConfig)
723                 || isServerCertUsedNetwork(wifiConfig)) {
724             return "";
725         }
726         int subId;
727         if (wifiConfig.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
728             // Config without carrierId use default data subscription.
729             subId = SubscriptionManager.getDefaultSubscriptionId();
730         } else {
731             subId = getSubIdForConfig(context, wifiConfig);
732         }
733         if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
734                 || isImsiPrivacyProtectionProvided(context, subId)) {
735             return "";
736         }
737 
738         // IMSI protection is not provided, return warning message.
739         return NonSdkApiWrapper.linkifyAnnotation(context, context.getText(
740                 R.string.wifitrackerlib_imsi_protection_warning), "url",
741                 context.getString(R.string.wifitrackerlib_help_url_imsi_protection));
742     }
743 
744     // Various utility methods copied from com.android.server.wifi.util.ScanResultUtils for
745     // extracting SecurityType from ScanResult.
746 
747     /**
748      * Helper method to check if the provided |scanResult| corresponds to a PSK network or not.
749      * This checks if the provided capabilities string contains PSK encryption type or not.
750      */
isScanResultForPskNetwork(ScanResult scanResult)751     private static boolean isScanResultForPskNetwork(ScanResult scanResult) {
752         return scanResult.capabilities.contains("PSK");
753     }
754 
755     /**
756      * Helper method to check if the provided |scanResult| corresponds to a WAPI-PSK network or not.
757      * This checks if the provided capabilities string contains PSK encryption type or not.
758      */
isScanResultForWapiPskNetwork(ScanResult scanResult)759     private static boolean isScanResultForWapiPskNetwork(ScanResult scanResult) {
760         return scanResult.capabilities.contains("WAPI-PSK");
761     }
762 
763     /**
764      * Helper method to check if the provided |scanResult| corresponds to a WAPI-CERT
765      * network or not.
766      * This checks if the provided capabilities string contains PSK encryption type or not.
767      */
isScanResultForWapiCertNetwork(ScanResult scanResult)768     private static boolean isScanResultForWapiCertNetwork(ScanResult scanResult) {
769         return scanResult.capabilities.contains("WAPI-CERT");
770     }
771 
772     /**
773      * Helper method to check if the provided |scanResult| corresponds to a EAP network or not.
774      * This checks these conditions:
775      * - Enable EAP/SHA1, EAP/SHA256 AKM, FT/EAP, or EAP-FILS.
776      * - Not a WPA3 Enterprise only network.
777      * - Not a WPA3 Enterprise transition network.
778      */
isScanResultForEapNetwork(ScanResult scanResult)779     private static boolean isScanResultForEapNetwork(ScanResult scanResult) {
780         return (scanResult.capabilities.contains("EAP/SHA1")
781                 || scanResult.capabilities.contains("EAP/SHA256")
782                 || scanResult.capabilities.contains("FT/EAP")
783                 || scanResult.capabilities.contains("EAP-FILS"))
784                 && !isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)
785                 && !isScanResultForWpa3EnterpriseTransitionNetwork(scanResult);
786     }
787 
isScanResultForPmfMandatoryNetwork(ScanResult scanResult)788     private static boolean isScanResultForPmfMandatoryNetwork(ScanResult scanResult) {
789         return scanResult.capabilities.contains("[MFPR]");
790     }
791 
isScanResultForPmfCapableNetwork(ScanResult scanResult)792     private static boolean isScanResultForPmfCapableNetwork(ScanResult scanResult) {
793         return scanResult.capabilities.contains("[MFPC]");
794     }
795 
796     /**
797      * Helper method to check if the provided |scanResult| corresponds to
798      * a WPA3 Enterprise transition network or not.
799      *
800      * See Section 3.3 WPA3-Enterprise transition mode in WPA3 Specification
801      * - Enable at least EAP/SHA1 and EAP/SHA256 AKM suites.
802      * - Not enable WPA1 version 1, WEP, and TKIP.
803      * - Management Frame Protection Capable is set.
804      * - Management Frame Protection Required is not set.
805      */
isScanResultForWpa3EnterpriseTransitionNetwork(ScanResult scanResult)806     private static boolean isScanResultForWpa3EnterpriseTransitionNetwork(ScanResult scanResult) {
807         return scanResult.capabilities.contains("EAP/SHA1")
808                 && scanResult.capabilities.contains("EAP/SHA256")
809                 && scanResult.capabilities.contains("RSN")
810                 && !scanResult.capabilities.contains("WEP")
811                 && !scanResult.capabilities.contains("TKIP")
812                 && !isScanResultForPmfMandatoryNetwork(scanResult)
813                 && isScanResultForPmfCapableNetwork(scanResult);
814     }
815 
816     /**
817      * Helper method to check if the provided |scanResult| corresponds to
818      * a WPA3 Enterprise only network or not.
819      *
820      * See Section 3.2 WPA3-Enterprise only mode in WPA3 Specification
821      * - Enable at least EAP/SHA256 AKM suite.
822      * - Not enable EAP/SHA1 AKM suite.
823      * - Not enable WPA1 version 1, WEP, and TKIP.
824      * - Management Frame Protection Capable is set.
825      * - Management Frame Protection Required is set.
826      */
isScanResultForWpa3EnterpriseOnlyNetwork(ScanResult scanResult)827     private static boolean isScanResultForWpa3EnterpriseOnlyNetwork(ScanResult scanResult) {
828         return scanResult.capabilities.contains("EAP/SHA256")
829                 && !scanResult.capabilities.contains("EAP/SHA1")
830                 && scanResult.capabilities.contains("RSN")
831                 && !scanResult.capabilities.contains("WEP")
832                 && !scanResult.capabilities.contains("TKIP")
833                 && isScanResultForPmfMandatoryNetwork(scanResult)
834                 && isScanResultForPmfCapableNetwork(scanResult);
835     }
836 
837 
838     /**
839      * Helper method to check if the provided |scanResult| corresponds to a WPA3-Enterprise 192-bit
840      * mode network or not.
841      * This checks if the provided capabilities comply these conditions:
842      * - Enable SUITE-B-192 AKM.
843      * - Not enable EAP/SHA1 AKM suite.
844      * - Not enable WPA1 version 1, WEP, and TKIP.
845      * - Management Frame Protection Required is set.
846      */
isScanResultForEapSuiteBNetwork(ScanResult scanResult)847     private static boolean isScanResultForEapSuiteBNetwork(ScanResult scanResult) {
848         return scanResult.capabilities.contains("SUITE_B_192")
849                 && scanResult.capabilities.contains("RSN")
850                 && !scanResult.capabilities.contains("WEP")
851                 && !scanResult.capabilities.contains("TKIP")
852                 && isScanResultForPmfMandatoryNetwork(scanResult);
853     }
854 
855     /**
856      * Helper method to check if the provided |scanResult| corresponds to a WEP network or not.
857      * This checks if the provided capabilities string contains WEP encryption type or not.
858      */
isScanResultForWepNetwork(ScanResult scanResult)859     private static boolean isScanResultForWepNetwork(ScanResult scanResult) {
860         return scanResult.capabilities.contains("WEP");
861     }
862 
863     /**
864      * Helper method to check if the provided |scanResult| corresponds to OWE network.
865      * This checks if the provided capabilities string contains OWE or not.
866      */
isScanResultForOweNetwork(ScanResult scanResult)867     private static boolean isScanResultForOweNetwork(ScanResult scanResult) {
868         return scanResult.capabilities.contains("OWE");
869     }
870 
871     /**
872      * Helper method to check if the provided |scanResult| corresponds to OWE transition network.
873      * This checks if the provided capabilities string contains OWE_TRANSITION or not.
874      */
isScanResultForOweTransitionNetwork(ScanResult scanResult)875     private static boolean isScanResultForOweTransitionNetwork(ScanResult scanResult) {
876         return scanResult.capabilities.contains("OWE_TRANSITION");
877     }
878 
879     /**
880      * Helper method to check if the provided |scanResult| corresponds to SAE network.
881      * This checks if the provided capabilities string contains SAE or not.
882      */
isScanResultForSaeNetwork(ScanResult scanResult)883     private static boolean isScanResultForSaeNetwork(ScanResult scanResult) {
884         return scanResult.capabilities.contains("SAE");
885     }
886 
887     /**
888      * Helper method to check if the provided |scanResult| corresponds to PSK-SAE transition
889      * network. This checks if the provided capabilities string contains both PSK and SAE or not.
890      */
isScanResultForPskSaeTransitionNetwork(ScanResult scanResult)891     private static boolean isScanResultForPskSaeTransitionNetwork(ScanResult scanResult) {
892         return scanResult.capabilities.contains("PSK") && scanResult.capabilities.contains("SAE");
893     }
894 
895     /**
896      *  Helper method to check if the provided |scanResult| corresponds to an unknown amk network.
897      *  This checks if the provided capabilities string contains ? or not.
898      */
isScanResultForUnknownAkmNetwork(ScanResult scanResult)899     private static boolean isScanResultForUnknownAkmNetwork(ScanResult scanResult) {
900         return scanResult.capabilities.contains("?");
901     }
902 
903     /**
904      * Helper method to check if the provided |scanResult| corresponds to an open network or not.
905      * This checks if the provided capabilities string does not contain either of WEP, PSK, SAE
906      * EAP, or unknown encryption types or not.
907      */
isScanResultForOpenNetwork(ScanResult scanResult)908     private static boolean isScanResultForOpenNetwork(ScanResult scanResult) {
909         return (!(isScanResultForWepNetwork(scanResult) || isScanResultForPskNetwork(scanResult)
910                 || isScanResultForEapNetwork(scanResult) || isScanResultForSaeNetwork(scanResult)
911                 || isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)
912                 || isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)
913                 || isScanResultForWapiPskNetwork(scanResult)
914                 || isScanResultForWapiCertNetwork(scanResult)
915                 || isScanResultForEapSuiteBNetwork(scanResult)
916                 || isScanResultForUnknownAkmNetwork(scanResult)));
917     }
918 
919     /**
920      * Get InetAddress masked with prefixLength.  Will never return null.
921      * @param address the IP address to mask with
922      * @param prefixLength the prefixLength used to mask the IP
923      */
getNetworkPart(InetAddress address, int prefixLength)924     public static InetAddress getNetworkPart(InetAddress address, int prefixLength) {
925         byte[] array = address.getAddress();
926         maskRawAddress(array, prefixLength);
927 
928         InetAddress netPart = null;
929         try {
930             netPart = InetAddress.getByAddress(array);
931         } catch (UnknownHostException e) {
932             throw new IllegalArgumentException("getNetworkPart error - " + e.toString());
933         }
934         return netPart;
935     }
936 
937     /**
938      *  Masks a raw IP address byte array with the specified prefix length.
939      */
maskRawAddress(byte[] array, int prefixLength)940     public static void maskRawAddress(byte[] array, int prefixLength) {
941         if (prefixLength < 0 || prefixLength > array.length * 8) {
942             throw new IllegalArgumentException("IP address with " + array.length
943                     + " bytes has invalid prefix length " + prefixLength);
944         }
945 
946         int offset = prefixLength / 8;
947         int remainder = prefixLength % 8;
948         byte mask = (byte) (0xFF << (8 - remainder));
949 
950         if (offset < array.length) {
951             array[offset] = (byte) (array[offset] & mask);
952         }
953 
954         offset++;
955 
956         for (; offset < array.length; offset++) {
957             array[offset] = 0;
958         }
959     }
960 
961     @Nullable
createPackageContextAsUser(int uid, Context context)962     private static Context createPackageContextAsUser(int uid, Context context) {
963         Context userContext = null;
964         try {
965             userContext = context.createPackageContextAsUser(context.getPackageName(), 0,
966                     UserHandle.getUserHandleForUid(uid));
967         } catch (PackageManager.NameNotFoundException e) {
968             return null;
969         }
970         return userContext;
971     }
972 
973     @Nullable
retrieveDevicePolicyManagerFromUserContext(int uid, Context context)974     private static DevicePolicyManager retrieveDevicePolicyManagerFromUserContext(int uid,
975             Context context) {
976         Context userContext = createPackageContextAsUser(uid, context);
977         if (userContext == null) return null;
978         return userContext.getSystemService(DevicePolicyManager.class);
979     }
980 
981     @Nullable
getDeviceOwner(Context context)982     private static Pair<UserHandle, ComponentName> getDeviceOwner(Context context) {
983         DevicePolicyManager devicePolicyManager =
984                 context.getSystemService(DevicePolicyManager.class);
985         if (devicePolicyManager == null) return null;
986         UserHandle deviceOwnerUser = null;
987         ComponentName deviceOwnerComponent = null;
988         try {
989             deviceOwnerUser = devicePolicyManager.getDeviceOwnerUser();
990             deviceOwnerComponent = devicePolicyManager.getDeviceOwnerComponentOnAnyUser();
991         } catch (Exception e) {
992             throw new RuntimeException("getDeviceOwner error - " + e.toString());
993         }
994         if (deviceOwnerUser == null || deviceOwnerComponent == null) return null;
995 
996         if (deviceOwnerComponent.getPackageName() == null) {
997             // shouldn't happen
998             return null;
999         }
1000         return new Pair<>(deviceOwnerUser, deviceOwnerComponent);
1001     }
1002 
1003     /**
1004      * Returns true if the |callingUid|/|callingPackage| is the device owner.
1005      */
isDeviceOwner(int uid, @Nullable String packageName, Context context)1006     public static boolean isDeviceOwner(int uid, @Nullable String packageName, Context context) {
1007         // Cannot determine if the app is DO/PO if packageName is null. So, will return false to be
1008         // safe.
1009         if (packageName == null) {
1010             return false;
1011         }
1012         Pair<UserHandle, ComponentName> deviceOwner = getDeviceOwner(context);
1013 
1014         // no device owner
1015         if (deviceOwner == null) return false;
1016 
1017         return deviceOwner.first.equals(UserHandle.getUserHandleForUid(uid))
1018                 && deviceOwner.second.getPackageName().equals(packageName);
1019     }
1020 
1021     /**
1022      * Returns true if the |callingUid|/|callingPackage| is the profile owner.
1023      */
isProfileOwner(int uid, @Nullable String packageName, Context context)1024     public static boolean isProfileOwner(int uid, @Nullable String packageName, Context context) {
1025         // Cannot determine if the app is DO/PO if packageName is null. So, will return false to be
1026         // safe.
1027         if (packageName == null) {
1028             return false;
1029         }
1030         DevicePolicyManager devicePolicyManager =
1031                 retrieveDevicePolicyManagerFromUserContext(uid, context);
1032         if (devicePolicyManager == null) return false;
1033         return devicePolicyManager.isProfileOwnerApp(packageName);
1034     }
1035 
1036     /**
1037      * Returns true if the |callingUid|/|callingPackage| is the device or profile owner.
1038      */
isDeviceOrProfileOwner(int uid, String packageName, Context context)1039     public static boolean isDeviceOrProfileOwner(int uid, String packageName, Context context) {
1040         return isDeviceOwner(uid, packageName, context)
1041                 || isProfileOwner(uid, packageName, context);
1042     }
1043 
1044     /**
1045      * Unknown security type that cannot be converted to
1046      * DevicePolicyManager.WifiSecurity security type.
1047      */
1048     public static final int DPM_SECURITY_TYPE_UNKNOWN = -1;
1049 
1050     /**
1051      * Utility method to convert WifiInfo.SecurityType to DevicePolicyManager.WifiSecurity
1052      * @param securityType WifiInfo.SecurityType to convert
1053      * @return DevicePolicyManager.WifiSecurity security level, or
1054      * {@link WifiInfo#DPM_SECURITY_TYPE_UNKNOWN} for unknown security types
1055      */
1056     @RequiresApi(Build.VERSION_CODES.TIRAMISU)
convertSecurityTypeToDpmWifiSecurity(int securityType)1057     public static int convertSecurityTypeToDpmWifiSecurity(int securityType) {
1058         switch (securityType) {
1059             case WifiInfo.SECURITY_TYPE_OPEN:
1060             case WifiInfo.SECURITY_TYPE_OWE:
1061                 return DevicePolicyManager.WIFI_SECURITY_OPEN;
1062             case WifiInfo.SECURITY_TYPE_WEP:
1063             case WifiInfo.SECURITY_TYPE_PSK:
1064             case WifiInfo.SECURITY_TYPE_SAE:
1065             case WifiInfo.SECURITY_TYPE_WAPI_PSK:
1066                 return DevicePolicyManager.WIFI_SECURITY_PERSONAL;
1067             case WifiInfo.SECURITY_TYPE_EAP:
1068             case WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE:
1069             case WifiInfo.SECURITY_TYPE_PASSPOINT_R1_R2:
1070             case WifiInfo.SECURITY_TYPE_PASSPOINT_R3:
1071             case WifiInfo.SECURITY_TYPE_WAPI_CERT:
1072                 return DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_EAP;
1073             case WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT:
1074                 return DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_192;
1075             default:
1076                 return DPM_SECURITY_TYPE_UNKNOWN;
1077         }
1078     }
1079 
1080     /**
1081      * Converts a ScanResult.WIFI_STANDARD_ value to a display string.
1082      */
getStandardString(@onNull Context context, int standard)1083     public static String getStandardString(@NonNull Context context, int standard) {
1084         switch (standard) {
1085             case ScanResult.WIFI_STANDARD_LEGACY:
1086                 return context.getString(R.string.wifitrackerlib_wifi_standard_legacy);
1087             case ScanResult.WIFI_STANDARD_11N:
1088                 return context.getString(R.string.wifitrackerlib_wifi_standard_11n);
1089             case ScanResult.WIFI_STANDARD_11AC:
1090                 return context.getString(R.string.wifitrackerlib_wifi_standard_11ac);
1091             case ScanResult.WIFI_STANDARD_11AX:
1092                 return context.getString(R.string.wifitrackerlib_wifi_standard_11ax);
1093             case ScanResult.WIFI_STANDARD_11AD:
1094                 return context.getString(R.string.wifitrackerlib_wifi_standard_11ad);
1095             case ScanResult.WIFI_STANDARD_11BE:
1096                 return context.getString(R.string.wifitrackerlib_wifi_standard_11be);
1097             default:
1098                 return context.getString(R.string.wifitrackerlib_wifi_standard_unknown);
1099         }
1100     }
1101 
1102     /**
1103      * Converts a frequency to one of
1104      *      {@link WifiScanner#WIFI_BAND_UNSPECIFIED},
1105      *      {@link WifiScanner#WIFI_BAND_24_GHZ},
1106      *      {@link WifiScanner#WIFI_BAND_5_GHZ},
1107      *      {@link WifiScanner#WIFI_BAND_6_GHZ}
1108      */
getBand(int freqMhz)1109     public static int getBand(int freqMhz) {
1110         if (freqMhz >= WifiEntry.MIN_FREQ_24GHZ && freqMhz < WifiEntry.MAX_FREQ_24GHZ) {
1111             return WifiScanner.WIFI_BAND_24_GHZ;
1112         } else if (freqMhz >= WifiEntry.MIN_FREQ_5GHZ && freqMhz < WifiEntry.MAX_FREQ_5GHZ) {
1113             return WifiScanner.WIFI_BAND_5_GHZ;
1114         } else if (freqMhz >= WifiEntry.MIN_FREQ_6GHZ && freqMhz < WifiEntry.MAX_FREQ_6GHZ) {
1115             return WifiScanner.WIFI_BAND_6_GHZ;
1116         } else {
1117             return WifiScanner.WIFI_BAND_UNSPECIFIED;
1118         }
1119     }
1120 
1121     /**
1122      * Converts one of
1123      *      {@link WifiScanner#WIFI_BAND_UNSPECIFIED},
1124      *      {@link WifiScanner#WIFI_BAND_24_GHZ},
1125      *      {@link WifiScanner#WIFI_BAND_5_GHZ},
1126      *      {@link WifiScanner#WIFI_BAND_6_GHZ}
1127      * to the display string of the corresponding Wi-Fi band.
1128      */
bandToBandString(@onNull Context context, int scannerBand)1129     public static String bandToBandString(@NonNull Context context, int scannerBand) {
1130         switch (scannerBand) {
1131             case WifiScanner.WIFI_BAND_24_GHZ:
1132                 return context.getResources().getString(R.string.wifitrackerlib_wifi_band_24_ghz);
1133             case WifiScanner.WIFI_BAND_5_GHZ:
1134                 return context.getResources().getString(R.string.wifitrackerlib_wifi_band_5_ghz);
1135             case WifiScanner.WIFI_BAND_6_GHZ:
1136                 return context.getResources().getString(R.string.wifitrackerlib_wifi_band_6_ghz);
1137             default:
1138                 return context.getResources().getString(R.string.wifitrackerlib_wifi_band_unknown);
1139         }
1140     }
1141 
1142     /**
1143      * Converts a frequency in MHz to the display string of the corresponding Wi-Fi band.
1144      */
frequencyToBandString(@onNull Context context, int freqMhz)1145     public static String frequencyToBandString(@NonNull Context context, int freqMhz) {
1146         return bandToBandString(context, getBand(freqMhz));
1147     }
1148 
1149     /**
1150      * Converts the band info in WifiInfo to the display string of the corresponding Wi-Fi band(s).
1151      */
wifiInfoToBandString( @onNull Context context, @NonNull WifiInfo wifiInfo)1152     public static String wifiInfoToBandString(
1153             @NonNull Context context, @NonNull WifiInfo wifiInfo) {
1154         if (!BuildCompat.isAtLeastU()) {
1155             return frequencyToBandString(context, wifiInfo.getFrequency());
1156         }
1157 
1158         StringJoiner sj = new StringJoiner(
1159                 context.getResources().getString(R.string.wifitrackerlib_multiband_separator));
1160         wifiInfo.getAssociatedMloLinks().stream()
1161                 .filter((link) -> link.getState() == MloLink.MLO_LINK_STATE_ACTIVE)
1162                 .map(MloLink::getBand)
1163                 .distinct()
1164                 .sorted()
1165                 .forEach((band) -> sj.add(bandToBandString(context, band)));
1166         if (sj.length() == 0) {
1167             return frequencyToBandString(context, wifiInfo.getFrequency());
1168         }
1169         return sj.toString();
1170     }
1171 
1172     /**
1173      * Returns the link speed string of the WifiInfo for Tx if isTx is {@code true}, else
1174      * return the Rx link speed.
1175      */
getSpeedString( @onNull Context context, @Nullable WifiInfo wifiInfo, boolean isTx)1176     public static String getSpeedString(
1177             @NonNull Context context, @Nullable WifiInfo wifiInfo, boolean isTx) {
1178         if (wifiInfo == null) {
1179             return "";
1180         }
1181         int wifiInfoSpeedMbps =
1182                 isTx ? wifiInfo.getTxLinkSpeedMbps() : wifiInfo.getRxLinkSpeedMbps();
1183         if (wifiInfoSpeedMbps <= 0) {
1184             return "";
1185         }
1186         if (!BuildCompat.isAtLeastU()) {
1187             return context.getString(R.string.wifitrackerlib_link_speed_mbps,
1188                     wifiInfoSpeedMbps);
1189         }
1190         List<MloLink> activeMloLinks = wifiInfo.getAssociatedMloLinks().stream()
1191                 .filter((link) -> link.getState() == MloLink.MLO_LINK_STATE_ACTIVE)
1192                 .collect(toList());
1193         if (activeMloLinks.size() <= 1) {
1194             return context.getString(R.string.wifitrackerlib_link_speed_mbps,
1195                     wifiInfoSpeedMbps);
1196         }
1197         StringJoiner sj = new StringJoiner(
1198                 context.getString(R.string.wifitrackerlib_multiband_separator));
1199         for (MloLink link : activeMloLinks) {
1200             int linkSpeedMbps = isTx ? link.getTxLinkSpeedMbps() : link.getRxLinkSpeedMbps();
1201             if (linkSpeedMbps <= 0) {
1202                 continue;
1203             }
1204             sj.add(context.getString(
1205                     R.string.wifitrackerlib_link_speed_on_band,
1206                     context.getString(R.string.wifitrackerlib_link_speed_mbps, linkSpeedMbps),
1207                     bandToBandString(context, link.getBand())));
1208         }
1209         return sj.toString();
1210     }
1211 
1212     /**
1213      * Gets the WifiInfo from a NetworkCapabilities if there is one.
1214      */
getWifiInfo(@onNull NetworkCapabilities capabilities)1215     public static WifiInfo getWifiInfo(@NonNull NetworkCapabilities capabilities) {
1216         TransportInfo transportInfo = capabilities.getTransportInfo();
1217         if (transportInfo instanceof WifiInfo) {
1218             return (WifiInfo) transportInfo;
1219         }
1220         return null;
1221     }
1222 
1223     /**
1224      * Converts security types to a display string.
1225      */
getSecurityString(@onNull Context context, @NonNull List<Integer> securityTypes, boolean concise)1226     public static String getSecurityString(@NonNull Context context,
1227             @NonNull List<Integer> securityTypes, boolean concise) {
1228         if (securityTypes.size() == 0) {
1229             return concise ? "" : context.getString(R.string.wifitrackerlib_wifi_security_none);
1230         }
1231         if (securityTypes.size() == 1) {
1232             final int security = securityTypes.get(0);
1233             switch(security) {
1234                 case SECURITY_TYPE_EAP:
1235                     return concise ? context.getString(
1236                             R.string.wifitrackerlib_wifi_security_short_eap_wpa_wpa2) :
1237                             context.getString(
1238                                     R.string.wifitrackerlib_wifi_security_eap_wpa_wpa2);
1239                 case SECURITY_TYPE_EAP_WPA3_ENTERPRISE:
1240                     return concise ? context.getString(
1241                             R.string.wifitrackerlib_wifi_security_short_eap_wpa3) :
1242                             context.getString(
1243                                     R.string.wifitrackerlib_wifi_security_eap_wpa3);
1244                 case SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT:
1245                     return concise ? context.getString(
1246                             R.string.wifitrackerlib_wifi_security_short_eap_suiteb) :
1247                             context.getString(R.string.wifitrackerlib_wifi_security_eap_suiteb);
1248                 case SECURITY_TYPE_PSK:
1249                     return concise ? context.getString(
1250                             R.string.wifitrackerlib_wifi_security_short_wpa_wpa2) :
1251                             context.getString(
1252                                     R.string.wifitrackerlib_wifi_security_wpa_wpa2);
1253                 case SECURITY_TYPE_WEP:
1254                     return context.getString(R.string.wifitrackerlib_wifi_security_wep);
1255                 case SECURITY_TYPE_SAE:
1256                     return concise ? context.getString(
1257                             R.string.wifitrackerlib_wifi_security_short_sae) :
1258                             context.getString(R.string.wifitrackerlib_wifi_security_sae);
1259                 case SECURITY_TYPE_OWE:
1260                     return concise ? context.getString(
1261                             R.string.wifitrackerlib_wifi_security_short_owe) :
1262                             context.getString(R.string.wifitrackerlib_wifi_security_owe);
1263                 case SECURITY_TYPE_OPEN:
1264                     return concise ? "" : context.getString(
1265                             R.string.wifitrackerlib_wifi_security_none);
1266             }
1267         }
1268         if (securityTypes.size() == 2) {
1269             if (securityTypes.contains(SECURITY_TYPE_OPEN)
1270                     && securityTypes.contains(SECURITY_TYPE_OWE)) {
1271                 StringJoiner sj = new StringJoiner("/");
1272                 sj.add(context.getString(R.string.wifitrackerlib_wifi_security_none));
1273                 sj.add(concise ? context.getString(
1274                         R.string.wifitrackerlib_wifi_security_short_owe) :
1275                         context.getString(R.string.wifitrackerlib_wifi_security_owe));
1276                 return sj.toString();
1277             }
1278             if (securityTypes.contains(SECURITY_TYPE_PSK)
1279                     && securityTypes.contains(SECURITY_TYPE_SAE)) {
1280                 return concise ? context.getString(
1281                         R.string.wifitrackerlib_wifi_security_short_wpa_wpa2_wpa3) :
1282                         context.getString(
1283                                 R.string.wifitrackerlib_wifi_security_wpa_wpa2_wpa3);
1284             }
1285             if (securityTypes.contains(SECURITY_TYPE_EAP)
1286                     && securityTypes.contains(SECURITY_TYPE_EAP_WPA3_ENTERPRISE)) {
1287                 return concise ? context.getString(
1288                         R.string.wifitrackerlib_wifi_security_short_eap_wpa_wpa2_wpa3) :
1289                         context.getString(
1290                                 R.string.wifitrackerlib_wifi_security_eap_wpa_wpa2_wpa3);
1291             }
1292         }
1293         // Unknown security types
1294         return concise ? "" : context.getString(R.string.wifitrackerlib_wifi_security_none);
1295     }
1296 
1297     /**
1298      * Returns the CertificateInfo for a WifiEnterpriseConfig.
1299      */
1300     @Nullable
getCertificateInfo( @onNull WifiEnterpriseConfig config)1301     public static WifiEntry.CertificateInfo getCertificateInfo(
1302             @NonNull WifiEnterpriseConfig config) {
1303         if (Build.VERSION.SDK_INT < 33) {
1304             return null;
1305         }
1306 
1307         // If the EAP method is not using certificate based connection, return null.
1308         if (!config.isEapMethodServerCertUsed() || !config.hasCaCertificate()) {
1309             return null;
1310         }
1311 
1312         WifiEntry.CertificateInfo info = new WifiEntry.CertificateInfo();
1313         info.domain = config.getDomainSuffixMatch();
1314         info.caCertificateAliases = config.getCaCertificateAliases();
1315         if (info.caCertificateAliases != null) {
1316             if (info.caCertificateAliases.length == 1
1317                     && info.caCertificateAliases[0].startsWith("hash://server/sha256/")) {
1318                 info.validationMethod = CERTIFICATE_VALIDATION_METHOD_USING_CERTIFICATE_PINNING;
1319                 info.caCertificateAliases = null;
1320             } else {
1321                 info.validationMethod = CERTIFICATE_VALIDATION_METHOD_USING_INSTALLED_ROOTCA;
1322             }
1323             return info;
1324         }
1325 
1326         if (config.getCaPath() != null) {
1327             info.validationMethod = CERTIFICATE_VALIDATION_METHOD_USING_SYSTEM_CERTIFICATE;
1328             return info;
1329         }
1330 
1331         return null;
1332     }
1333 }
1334