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