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