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