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