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