blob: 22c3765d6a49e06d3f19db0b4ae9a7051ce68e1d [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.wifitrackerlib;
import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED;
import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_PERMANENTLY_DISABLED;
import static java.util.Comparator.comparingInt;
import android.app.admin.DevicePolicyManager;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.net.ConnectivityDiagnosticsManager;
import android.net.NetworkCapabilities;
import android.net.NetworkInfo;
import android.net.NetworkInfo.DetailedState;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiConfiguration;
import android.net.wifi.WifiConfiguration.NetworkSelectionStatus;
import android.net.wifi.WifiInfo;
import android.os.Build;
import android.os.PersistableBundle;
import android.os.UserHandle;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.text.TextUtils;
import android.text.format.DateUtils;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.StringJoiner;
/**
* Utility methods for WifiTrackerLib.
*/
public class Utils {
// TODO(b/242144920): remove this after publishing this reason in U.
// This reason is added in U and hidden in T, using a hard-coded value first.
public static final int DISABLED_TRANSITION_DISABLE_INDICATION = 13;
// Returns the ScanResult with the best RSSI from a list of ScanResults.
@Nullable
public static ScanResult getBestScanResultByLevel(@NonNull List<ScanResult> scanResults) {
if (scanResults.isEmpty()) return null;
return Collections.max(scanResults, comparingInt(scanResult -> scanResult.level));
}
// Returns a list of WifiInfo SECURITY_TYPE_* supported by a ScanResult.
@NonNull
public static List<Integer> getSecurityTypesFromScanResult(@NonNull ScanResult scanResult) {
List<Integer> securityTypes = new ArrayList<>();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
for (int securityType : scanResult.getSecurityTypes()) {
securityTypes.add(securityType);
}
return securityTypes;
}
// Open network & its upgradable types
if (isScanResultForOweTransitionNetwork(scanResult)) {
securityTypes.add(WifiInfo.SECURITY_TYPE_OPEN);
securityTypes.add(WifiInfo.SECURITY_TYPE_OWE);
return securityTypes;
} else if (isScanResultForOweNetwork(scanResult)) {
securityTypes.add(WifiInfo.SECURITY_TYPE_OWE);
return securityTypes;
} else if (isScanResultForOpenNetwork(scanResult)) {
securityTypes.add(WifiInfo.SECURITY_TYPE_OPEN);
return securityTypes;
}
// WEP network which has no upgradable type
if (isScanResultForWepNetwork(scanResult)) {
securityTypes.add(WifiInfo.SECURITY_TYPE_WEP);
return securityTypes;
}
// WAPI PSK network which has no upgradable type
if (isScanResultForWapiPskNetwork(scanResult)) {
securityTypes.add(WifiInfo.SECURITY_TYPE_WAPI_PSK);
return securityTypes;
}
// WAPI CERT network which has no upgradable type
if (isScanResultForWapiCertNetwork(scanResult)) {
securityTypes.add(
WifiInfo.SECURITY_TYPE_WAPI_CERT);
return securityTypes;
}
// WPA2 personal network & its upgradable types
if (isScanResultForPskNetwork(scanResult)
&& isScanResultForSaeNetwork(scanResult)) {
securityTypes.add(WifiInfo.SECURITY_TYPE_PSK);
securityTypes.add(WifiInfo.SECURITY_TYPE_SAE);
return securityTypes;
} else if (isScanResultForPskNetwork(scanResult)) {
securityTypes.add(WifiInfo.SECURITY_TYPE_PSK);
return securityTypes;
} else if (isScanResultForSaeNetwork(scanResult)) {
securityTypes.add(WifiInfo.SECURITY_TYPE_SAE);
return securityTypes;
}
// WPA3 Enterprise 192-bit mode, WPA2/WPA3 enterprise network & its upgradable types
if (isScanResultForEapSuiteBNetwork(scanResult)) {
securityTypes.add(WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
} else if (isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)) {
securityTypes.add(WifiInfo.SECURITY_TYPE_EAP);
securityTypes.add(WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
} else if (isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)) {
securityTypes.add(WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
} else if (isScanResultForEapNetwork(scanResult)) {
securityTypes.add(WifiInfo.SECURITY_TYPE_EAP);
}
return securityTypes;
}
// Returns a list of WifiInfo SECURITY_TYPE_* supported by a WifiConfiguration
// TODO(b/187755473): Use new public APIs to get the security type instead of relying on the
// legacy allowedKeyManagement bitset.
static List<Integer> getSecurityTypesFromWifiConfiguration(@NonNull WifiConfiguration config) {
if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WAPI_CERT)) {
return Arrays.asList(WifiInfo.SECURITY_TYPE_WAPI_CERT);
} else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WAPI_PSK)) {
return Arrays.asList(WifiInfo.SECURITY_TYPE_WAPI_PSK);
} else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SUITE_B_192)) {
return Arrays.asList(WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT);
} else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.OWE)) {
return Arrays.asList(WifiInfo.SECURITY_TYPE_OWE);
} else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.SAE)) {
return Arrays.asList(WifiInfo.SECURITY_TYPE_SAE);
} else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA2_PSK)) {
return Arrays.asList(WifiInfo.SECURITY_TYPE_PSK);
} else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_EAP)) {
if (config.requirePmf
&& !config.allowedPairwiseCiphers.get(WifiConfiguration.PairwiseCipher.TKIP)
&& config.allowedProtocols.get(WifiConfiguration.Protocol.RSN)) {
return Arrays.asList(WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
} else {
// WPA2 configs should also be valid for WPA3-Enterprise APs
return Arrays.asList(
WifiInfo.SECURITY_TYPE_EAP, WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE);
}
} else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.WPA_PSK)) {
return Arrays.asList(WifiInfo.SECURITY_TYPE_PSK);
} else if (config.allowedKeyManagement.get(WifiConfiguration.KeyMgmt.NONE)) {
if (config.wepKeys != null) {
for (int i = 0; i < config.wepKeys.length; i++) {
if (config.wepKeys[i] != null) {
return Arrays.asList(WifiInfo.SECURITY_TYPE_WEP);
}
}
}
}
return Arrays.asList(WifiInfo.SECURITY_TYPE_OPEN);
}
/**
* Returns a single WifiInfo security type from the list of multiple WifiInfo security
* types supported by an entry.
*
* Single security types will have a 1-to-1 mapping.
* Multiple security type networks will collapse to the lowest security type in the group:
* - Open/OWE -> Open
* - PSK/SAE -> PSK
* - EAP/EAP-WPA3 -> EAP
*/
static int getSingleSecurityTypeFromMultipleSecurityTypes(
@NonNull List<Integer> securityTypes) {
if (securityTypes.size() == 1) {
return securityTypes.get(0);
} else if (securityTypes.size() == 2) {
if (securityTypes.contains(WifiInfo.SECURITY_TYPE_OPEN)) {
return WifiInfo.SECURITY_TYPE_OPEN;
}
if (securityTypes.contains(WifiInfo.SECURITY_TYPE_PSK)) {
return WifiInfo.SECURITY_TYPE_PSK;
}
if (securityTypes.contains(WifiInfo.SECURITY_TYPE_EAP)) {
return WifiInfo.SECURITY_TYPE_EAP;
}
}
return WifiInfo.SECURITY_TYPE_UNKNOWN;
}
/**
* Get the app label for a suggestion/specifier package name, or an empty String if none exist
*/
static String getAppLabel(Context context, String packageName) {
try {
ApplicationInfo appInfo = context.getPackageManager().getApplicationInfo(
packageName,
0 /* flags */);
return appInfo.loadLabel(context.getPackageManager()).toString();
} catch (PackageManager.NameNotFoundException e) {
return "";
}
}
static String getConnectedDescription(@NonNull Context context,
@Nullable WifiConfiguration wifiConfiguration,
@Nullable NetworkCapabilities networkCapabilities,
boolean isDefaultNetwork,
boolean isLowQuality,
@Nullable ConnectivityDiagnosticsManager.ConnectivityReport connectivityReport) {
final StringJoiner sj = new StringJoiner(context.getString(
R.string.wifitrackerlib_summary_separator));
boolean shouldShowConnected = isDefaultNetwork;
if (wifiConfiguration != null
&& (wifiConfiguration.fromWifiNetworkSuggestion
|| wifiConfiguration.fromWifiNetworkSpecifier)) {
// For suggestion or specifier networks to show "Connected via ..."
final String suggestionOrSpecifierLabel =
getSuggestionOrSpecifierLabel(context, wifiConfiguration);
if (!TextUtils.isEmpty(suggestionOrSpecifierLabel)) {
if (!isDefaultNetwork) {
sj.add(context.getString(R.string.wifitrackerlib_available_via_app,
suggestionOrSpecifierLabel));
} else {
sj.add(context.getString(R.string.wifitrackerlib_connected_via_app,
suggestionOrSpecifierLabel));
}
shouldShowConnected = false;
}
}
if (isLowQuality) {
sj.add(context.getString(R.string.wifi_connected_low_quality));
shouldShowConnected = false;
}
// For displaying network capability info, such as captive portal or no internet
if (networkCapabilities != null) {
if (networkCapabilities.hasCapability(
NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL)) {
// "Sign in to network"
sj.add(context.getString(context.getResources()
.getIdentifier("network_available_sign_in", "string", "android")));
shouldShowConnected = false;
} else if (networkCapabilities.hasCapability(
NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY)) {
// "Limited connection..."
sj.add(context.getString(
R.string.wifitrackerlib_wifi_limited_connection));
shouldShowConnected = false;
} else if (!networkCapabilities.hasCapability(
NetworkCapabilities.NET_CAPABILITY_VALIDATED)) {
boolean noInternetExpected = wifiConfiguration != null
&& wifiConfiguration.isNoInternetAccessExpected();
if (connectivityReport == null && !noInternetExpected) {
// "Checking for internet access..."
sj.add(context.getString(R.string.wifitrackerlib_checking_for_internet_access));
shouldShowConnected = false;
} else if (networkCapabilities.isPrivateDnsBroken()) {
// "Private DNS server cannot be accessed"
sj.add(context.getString(R.string.wifitrackerlib_private_dns_broken));
shouldShowConnected = false;
} else if (noInternetExpected) {
// "Connected to device. Can't provide internet."
sj.add(context.getString(
R.string.wifitrackerlib_wifi_connected_cannot_provide_internet));
shouldShowConnected = false;
} else {
// "No internet access"
sj.add(context.getString(R.string.wifitrackerlib_wifi_no_internet));
}
}
}
// Show "Connected" first if we haven't hidden it due to other strings.
if (shouldShowConnected) {
return new StringJoiner(context.getString(R.string.wifitrackerlib_summary_separator))
.add(context.getResources().getStringArray(R.array.wifitrackerlib_wifi_status)
[DetailedState.CONNECTED.ordinal()]).merge(sj).toString();
}
return sj.toString();
}
static String getConnectingDescription(Context context, NetworkInfo networkInfo) {
if (context == null || networkInfo == null) {
return "";
}
DetailedState detailedState = networkInfo.getDetailedState();
if (detailedState == null) {
return "";
}
final String[] wifiStatusArray = context.getResources()
.getStringArray(R.array.wifitrackerlib_wifi_status);
final int index = detailedState.ordinal();
return index >= wifiStatusArray.length ? "" : wifiStatusArray[index];
}
static String getDisconnectedDescription(
@NonNull WifiTrackerInjector injector,
Context context,
WifiConfiguration wifiConfiguration,
boolean forSavedNetworksPage,
boolean concise) {
if (context == null || wifiConfiguration == null) {
return "";
}
final StringJoiner sj = new StringJoiner(context.getString(
R.string.wifitrackerlib_summary_separator));
// For "Saved", "Saved by ...", and "Available via..."
if (concise) {
sj.add(context.getString(R.string.wifitrackerlib_wifi_disconnected));
} else if (forSavedNetworksPage && !wifiConfiguration.isPasspoint()) {
if (!injector.getNoAttributionAnnotationPackages().contains(
wifiConfiguration.creatorName)) {
final CharSequence appLabel = getAppLabel(context,
wifiConfiguration.creatorName);
if (!TextUtils.isEmpty(appLabel)) {
sj.add(context.getString(R.string.wifitrackerlib_saved_network, appLabel));
}
}
} else {
if (wifiConfiguration.fromWifiNetworkSuggestion) {
final String suggestionOrSpecifierLabel =
getSuggestionOrSpecifierLabel(context, wifiConfiguration);
if (!TextUtils.isEmpty(suggestionOrSpecifierLabel)) {
sj.add(context.getString(
R.string.wifitrackerlib_available_via_app,
suggestionOrSpecifierLabel));
}
} else {
sj.add(context.getString(R.string.wifitrackerlib_wifi_remembered));
}
}
// For failure messages and disabled reasons
final String wifiConfigFailureMessage =
getWifiConfigurationFailureMessage(context, wifiConfiguration);
if (!TextUtils.isEmpty(wifiConfigFailureMessage)) {
sj.add(wifiConfigFailureMessage);
}
return sj.toString();
}
private static String getSuggestionOrSpecifierLabel(
Context context, WifiConfiguration wifiConfiguration) {
if (context == null || wifiConfiguration == null) {
return "";
}
final String carrierName = getCarrierNameForSubId(context,
getSubIdForConfig(context, wifiConfiguration));
if (!TextUtils.isEmpty(carrierName)) {
return carrierName;
}
final String suggestorLabel = getAppLabel(context, wifiConfiguration.creatorName);
if (!TextUtils.isEmpty(suggestorLabel)) {
return suggestorLabel;
}
// Fall-back to the package name in case the app label is missing
return wifiConfiguration.creatorName;
}
private static String getWifiConfigurationFailureMessage(
Context context, WifiConfiguration wifiConfiguration) {
if (context == null || wifiConfiguration == null) {
return "";
}
// Check for any failure messages to display
if (wifiConfiguration.hasNoInternetAccess()) {
int messageID =
wifiConfiguration.getNetworkSelectionStatus().getNetworkSelectionStatus()
== NETWORK_SELECTION_PERMANENTLY_DISABLED
? R.string.wifitrackerlib_wifi_no_internet_no_reconnect
: R.string.wifitrackerlib_wifi_no_internet;
return context.getString(messageID);
} else if (wifiConfiguration.getNetworkSelectionStatus().getNetworkSelectionStatus()
!= NETWORK_SELECTION_ENABLED) {
WifiConfiguration.NetworkSelectionStatus networkStatus =
wifiConfiguration.getNetworkSelectionStatus();
switch (networkStatus.getNetworkSelectionDisableReason()) {
case WifiConfiguration.NetworkSelectionStatus.DISABLED_AUTHENTICATION_FAILURE:
case WifiConfiguration.NetworkSelectionStatus
.DISABLED_AUTHENTICATION_NO_SUBSCRIPTION:
return context.getString(
R.string.wifitrackerlib_wifi_disabled_password_failure);
case WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD:
return context.getString(R.string.wifitrackerlib_wifi_check_password_try_again);
case WifiConfiguration.NetworkSelectionStatus.DISABLED_DHCP_FAILURE:
return context.getString(R.string.wifitrackerlib_wifi_disabled_network_failure);
case WifiConfiguration.NetworkSelectionStatus.DISABLED_ASSOCIATION_REJECTION:
return context.getString(R.string.wifitrackerlib_wifi_disabled_generic);
case WifiConfiguration.NetworkSelectionStatus.DISABLED_NO_INTERNET_PERMANENT:
case WifiConfiguration.NetworkSelectionStatus.DISABLED_NO_INTERNET_TEMPORARY:
return context.getString(R.string.wifitrackerlib_wifi_no_internet_no_reconnect);
case DISABLED_TRANSITION_DISABLE_INDICATION:
return context.getString(
R.string.wifitrackerlib_wifi_disabled_transition_disable_indication);
default:
break;
}
}
switch (wifiConfiguration.getRecentFailureReason()) {
case WifiConfiguration.RECENT_FAILURE_AP_UNABLE_TO_HANDLE_NEW_STA:
case WifiConfiguration.RECENT_FAILURE_REFUSED_TEMPORARILY:
case WifiConfiguration.RECENT_FAILURE_DISCONNECTION_AP_BUSY:
return context.getString(R.string
.wifitrackerlib_wifi_ap_unable_to_handle_new_sta);
case WifiConfiguration.RECENT_FAILURE_POOR_CHANNEL_CONDITIONS:
return context.getString(R.string.wifitrackerlib_wifi_poor_channel_conditions);
case WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_UNSPECIFIED:
case WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_AIR_INTERFACE_OVERLOADED:
case WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_AUTH_SERVER_OVERLOADED:
return context.getString(R.string
.wifitrackerlib_wifi_mbo_assoc_disallowed_cannot_connect);
case WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_MAX_NUM_STA_ASSOCIATED:
return context.getString(R.string
.wifitrackerlib_wifi_mbo_assoc_disallowed_max_num_sta_associated);
case WifiConfiguration.RECENT_FAILURE_MBO_ASSOC_DISALLOWED_INSUFFICIENT_RSSI:
case WifiConfiguration.RECENT_FAILURE_OCE_RSSI_BASED_ASSOCIATION_REJECTION:
return context.getString(R.string
.wifitrackerlib_wifi_mbo_oce_assoc_disallowed_insufficient_rssi);
case WifiConfiguration.RECENT_FAILURE_NETWORK_NOT_FOUND:
return context.getString(R.string.wifitrackerlib_wifi_network_not_found);
default:
// do nothing
}
return "";
}
static String getAutoConnectDescription(@NonNull Context context,
@NonNull WifiEntry wifiEntry) {
if (context == null || wifiEntry == null || !wifiEntry.canSetAutoJoinEnabled()) {
return "";
}
return wifiEntry.isAutoJoinEnabled()
? "" : context.getString(R.string.wifitrackerlib_auto_connect_disable);
}
static String getMeteredDescription(@NonNull Context context, @Nullable WifiEntry wifiEntry) {
if (context == null || wifiEntry == null) {
return "";
}
if (!wifiEntry.canSetMeteredChoice()
&& wifiEntry.getMeteredChoice() != WifiEntry.METERED_CHOICE_METERED) {
return "";
}
if (wifiEntry.getMeteredChoice() == WifiEntry.METERED_CHOICE_METERED) {
return context.getString(R.string.wifitrackerlib_wifi_metered_label);
} else if (wifiEntry.getMeteredChoice() == WifiEntry.METERED_CHOICE_UNMETERED) {
return context.getString(R.string.wifitrackerlib_wifi_unmetered_label);
} else { // METERED_CHOICE_AUTO
return wifiEntry.isMetered() ? context.getString(
R.string.wifitrackerlib_wifi_metered_label) : "";
}
}
static String getVerboseLoggingDescription(@NonNull WifiEntry wifiEntry) {
if (!BaseWifiTracker.isVerboseLoggingEnabled() || wifiEntry == null) {
return "";
}
final StringJoiner sj = new StringJoiner(" ");
final String wifiInfoDescription = wifiEntry.getWifiInfoDescription();
if (!TextUtils.isEmpty(wifiInfoDescription)) {
sj.add(wifiInfoDescription);
}
final String networkCapabilityDescription = wifiEntry.getNetworkCapabilityDescription();
if (!TextUtils.isEmpty(networkCapabilityDescription)) {
sj.add(networkCapabilityDescription);
}
final String scanResultsDescription = wifiEntry.getScanResultDescription();
if (!TextUtils.isEmpty(scanResultsDescription)) {
sj.add(scanResultsDescription);
}
final String networkSelectionDescription = wifiEntry.getNetworkSelectionDescription();
if (!TextUtils.isEmpty(networkSelectionDescription)) {
sj.add(networkSelectionDescription);
}
return sj.toString();
}
static String getNetworkSelectionDescription(WifiConfiguration wifiConfig) {
if (wifiConfig == null) {
return "";
}
StringBuilder description = new StringBuilder();
NetworkSelectionStatus networkSelectionStatus = wifiConfig.getNetworkSelectionStatus();
if (networkSelectionStatus.getNetworkSelectionStatus() != NETWORK_SELECTION_ENABLED) {
description.append(" (" + networkSelectionStatus.getNetworkStatusString());
if (networkSelectionStatus.getDisableTime() > 0) {
long now = System.currentTimeMillis();
long elapsedSeconds = (now - networkSelectionStatus.getDisableTime()) / 1000;
description.append(" " + DateUtils.formatElapsedTime(elapsedSeconds));
}
description.append(")");
}
int maxNetworkSelectionDisableReason =
NetworkSelectionStatus.getMaxNetworkSelectionDisableReason();
for (int reason = 0; reason <= maxNetworkSelectionDisableReason; reason++) {
int disableReasonCounter = networkSelectionStatus.getDisableReasonCounter(reason);
if (disableReasonCounter == 0) {
continue;
}
description.append(" ")
.append(NetworkSelectionStatus.getNetworkSelectionDisableReasonString(reason))
.append("=")
.append(disableReasonCounter);
}
return description.toString();
}
/**
* Returns the display string corresponding to the detailed state of the given NetworkInfo
*/
static String getNetworkDetailedState(Context context, NetworkInfo networkInfo) {
if (context == null || networkInfo == null) {
return "";
}
DetailedState detailedState = networkInfo.getDetailedState();
if (detailedState == null) {
return "";
}
String[] wifiStatusArray = context.getResources()
.getStringArray(R.array.wifitrackerlib_wifi_status);
int index = detailedState.ordinal();
return index >= wifiStatusArray.length ? "" : wifiStatusArray[index];
}
/**
* Check if the SIM is present for target carrier Id. If the carrierId is
* {@link TelephonyManager#UNKNOWN_CARRIER_ID}, then this returns true if there is any SIM
* present.
*/
static boolean isSimPresent(@NonNull Context context, int carrierId) {
SubscriptionManager subscriptionManager =
(SubscriptionManager) context.getSystemService(
Context.TELEPHONY_SUBSCRIPTION_SERVICE);
if (subscriptionManager == null) return false;
List<SubscriptionInfo> subInfoList = subscriptionManager.getActiveSubscriptionInfoList();
if (subInfoList == null || subInfoList.isEmpty()) {
return false;
}
if (carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
// Return true if any SIM is present for UNKNOWN_CARRIER_ID since the framework will
// match this to the default data SIM.
return true;
}
return subInfoList.stream()
.anyMatch(info -> info.getCarrierId() == carrierId);
}
/**
* Get the SIM carrier name for target subscription Id.
*/
static @Nullable String getCarrierNameForSubId(@NonNull Context context, int subId) {
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID) {
return null;
}
TelephonyManager telephonyManager =
(TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
if (telephonyManager == null) return null;
TelephonyManager specifiedTm = telephonyManager.createForSubscriptionId(subId);
if (specifiedTm == null) {
return null;
}
CharSequence name = specifiedTm.getSimCarrierIdName();
if (name == null) {
return null;
}
return name.toString();
}
static boolean isServerCertUsedNetwork(@NonNull WifiConfiguration config) {
return config.enterpriseConfig != null && config.enterpriseConfig
.isEapMethodServerCertUsed();
}
static boolean isSimCredential(@NonNull WifiConfiguration config) {
return config.enterpriseConfig != null
&& config.enterpriseConfig.isAuthenticationSimBased();
}
/**
* Get the best match subscription Id for target WifiConfiguration.
*/
static int getSubIdForConfig(@NonNull Context context, @NonNull WifiConfiguration config) {
if (config.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
SubscriptionManager subscriptionManager =
(SubscriptionManager) context.getSystemService(
Context.TELEPHONY_SUBSCRIPTION_SERVICE);
if (subscriptionManager == null) {
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
List<SubscriptionInfo> subInfoList = subscriptionManager.getActiveSubscriptionInfoList();
if (subInfoList == null || subInfoList.isEmpty()) {
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
}
int matchSubId = SubscriptionManager.INVALID_SUBSCRIPTION_ID;
int dataSubId = SubscriptionManager.getDefaultDataSubscriptionId();
for (SubscriptionInfo subInfo : subInfoList) {
if (subInfo.getCarrierId() == config.carrierId) {
matchSubId = subInfo.getSubscriptionId();
if (matchSubId == dataSubId) {
// Priority of Data sub is higher than non data sub.
break;
}
}
}
return matchSubId;
}
/**
* Check if target subscription Id requires IMSI privacy protection.
*/
static boolean isImsiPrivacyProtectionProvided(@NonNull Context context, int subId) {
CarrierConfigManager carrierConfigManager =
(CarrierConfigManager) context.getSystemService(Context.CARRIER_CONFIG_SERVICE);
if (carrierConfigManager == null) {
return false;
}
PersistableBundle bundle = carrierConfigManager.getConfigForSubId(subId);
if (bundle == null) {
return false;
}
return (bundle.getInt(CarrierConfigManager.IMSI_KEY_AVAILABILITY_INT)
& TelephonyManager.KEY_TYPE_WLAN) != 0;
}
static CharSequence getImsiProtectionDescription(Context context,
@Nullable WifiConfiguration wifiConfig) {
if (context == null || wifiConfig == null || !isSimCredential(wifiConfig)
|| isServerCertUsedNetwork(wifiConfig)) {
return "";
}
int subId;
if (wifiConfig.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) {
// Config without carrierId use default data subscription.
subId = SubscriptionManager.getDefaultSubscriptionId();
} else {
subId = getSubIdForConfig(context, wifiConfig);
}
if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
|| isImsiPrivacyProtectionProvided(context, subId)) {
return "";
}
// IMSI protection is not provided, return warning message.
return NonSdkApiWrapper.linkifyAnnotation(context, context.getText(
R.string.wifitrackerlib_imsi_protection_warning), "url",
context.getString(R.string.wifitrackerlib_help_url_imsi_protection));
}
// Various utility methods copied from com.android.server.wifi.util.ScanResultUtils for
// extracting SecurityType from ScanResult.
/**
* Helper method to check if the provided |scanResult| corresponds to a PSK network or not.
* This checks if the provided capabilities string contains PSK encryption type or not.
*/
private static boolean isScanResultForPskNetwork(ScanResult scanResult) {
return scanResult.capabilities.contains("PSK");
}
/**
* Helper method to check if the provided |scanResult| corresponds to a WAPI-PSK network or not.
* This checks if the provided capabilities string contains PSK encryption type or not.
*/
private static boolean isScanResultForWapiPskNetwork(ScanResult scanResult) {
return scanResult.capabilities.contains("WAPI-PSK");
}
/**
* Helper method to check if the provided |scanResult| corresponds to a WAPI-CERT
* network or not.
* This checks if the provided capabilities string contains PSK encryption type or not.
*/
private static boolean isScanResultForWapiCertNetwork(ScanResult scanResult) {
return scanResult.capabilities.contains("WAPI-CERT");
}
/**
* Helper method to check if the provided |scanResult| corresponds to a EAP network or not.
* This checks these conditions:
* - Enable EAP/SHA1, EAP/SHA256 AKM, FT/EAP, or EAP-FILS.
* - Not a WPA3 Enterprise only network.
* - Not a WPA3 Enterprise transition network.
*/
private static boolean isScanResultForEapNetwork(ScanResult scanResult) {
return (scanResult.capabilities.contains("EAP/SHA1")
|| scanResult.capabilities.contains("EAP/SHA256")
|| scanResult.capabilities.contains("FT/EAP")
|| scanResult.capabilities.contains("EAP-FILS"))
&& !isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)
&& !isScanResultForWpa3EnterpriseTransitionNetwork(scanResult);
}
private static boolean isScanResultForPmfMandatoryNetwork(ScanResult scanResult) {
return scanResult.capabilities.contains("[MFPR]");
}
private static boolean isScanResultForPmfCapableNetwork(ScanResult scanResult) {
return scanResult.capabilities.contains("[MFPC]");
}
/**
* Helper method to check if the provided |scanResult| corresponds to
* a WPA3 Enterprise transition network or not.
*
* See Section 3.3 WPA3-Enterprise transition mode in WPA3 Specification
* - Enable at least EAP/SHA1 and EAP/SHA256 AKM suites.
* - Not enable WPA1 version 1, WEP, and TKIP.
* - Management Frame Protection Capable is set.
* - Management Frame Protection Required is not set.
*/
private static boolean isScanResultForWpa3EnterpriseTransitionNetwork(ScanResult scanResult) {
return scanResult.capabilities.contains("EAP/SHA1")
&& scanResult.capabilities.contains("EAP/SHA256")
&& scanResult.capabilities.contains("RSN")
&& !scanResult.capabilities.contains("WEP")
&& !scanResult.capabilities.contains("TKIP")
&& !isScanResultForPmfMandatoryNetwork(scanResult)
&& isScanResultForPmfCapableNetwork(scanResult);
}
/**
* Helper method to check if the provided |scanResult| corresponds to
* a WPA3 Enterprise only network or not.
*
* See Section 3.2 WPA3-Enterprise only mode in WPA3 Specification
* - Enable at least EAP/SHA256 AKM suite.
* - Not enable EAP/SHA1 AKM suite.
* - Not enable WPA1 version 1, WEP, and TKIP.
* - Management Frame Protection Capable is set.
* - Management Frame Protection Required is set.
*/
private static boolean isScanResultForWpa3EnterpriseOnlyNetwork(ScanResult scanResult) {
return scanResult.capabilities.contains("EAP/SHA256")
&& !scanResult.capabilities.contains("EAP/SHA1")
&& scanResult.capabilities.contains("RSN")
&& !scanResult.capabilities.contains("WEP")
&& !scanResult.capabilities.contains("TKIP")
&& isScanResultForPmfMandatoryNetwork(scanResult)
&& isScanResultForPmfCapableNetwork(scanResult);
}
/**
* Helper method to check if the provided |scanResult| corresponds to a WPA3-Enterprise 192-bit
* mode network or not.
* This checks if the provided capabilities comply these conditions:
* - Enable SUITE-B-192 AKM.
* - Not enable EAP/SHA1 AKM suite.
* - Not enable WPA1 version 1, WEP, and TKIP.
* - Management Frame Protection Required is set.
*/
private static boolean isScanResultForEapSuiteBNetwork(ScanResult scanResult) {
return scanResult.capabilities.contains("SUITE_B_192")
&& scanResult.capabilities.contains("RSN")
&& !scanResult.capabilities.contains("WEP")
&& !scanResult.capabilities.contains("TKIP")
&& isScanResultForPmfMandatoryNetwork(scanResult);
}
/**
* Helper method to check if the provided |scanResult| corresponds to a WEP network or not.
* This checks if the provided capabilities string contains WEP encryption type or not.
*/
private static boolean isScanResultForWepNetwork(ScanResult scanResult) {
return scanResult.capabilities.contains("WEP");
}
/**
* Helper method to check if the provided |scanResult| corresponds to OWE network.
* This checks if the provided capabilities string contains OWE or not.
*/
private static boolean isScanResultForOweNetwork(ScanResult scanResult) {
return scanResult.capabilities.contains("OWE");
}
/**
* Helper method to check if the provided |scanResult| corresponds to OWE transition network.
* This checks if the provided capabilities string contains OWE_TRANSITION or not.
*/
private static boolean isScanResultForOweTransitionNetwork(ScanResult scanResult) {
return scanResult.capabilities.contains("OWE_TRANSITION");
}
/**
* Helper method to check if the provided |scanResult| corresponds to SAE network.
* This checks if the provided capabilities string contains SAE or not.
*/
private static boolean isScanResultForSaeNetwork(ScanResult scanResult) {
return scanResult.capabilities.contains("SAE");
}
/**
* Helper method to check if the provided |scanResult| corresponds to PSK-SAE transition
* network. This checks if the provided capabilities string contains both PSK and SAE or not.
*/
private static boolean isScanResultForPskSaeTransitionNetwork(ScanResult scanResult) {
return scanResult.capabilities.contains("PSK") && scanResult.capabilities.contains("SAE");
}
/**
* Helper method to check if the provided |scanResult| corresponds to an unknown amk network.
* This checks if the provided capabilities string contains ? or not.
*/
private static boolean isScanResultForUnknownAkmNetwork(ScanResult scanResult) {
return scanResult.capabilities.contains("?");
}
/**
* Helper method to check if the provided |scanResult| corresponds to an open network or not.
* This checks if the provided capabilities string does not contain either of WEP, PSK, SAE
* EAP, or unknown encryption types or not.
*/
private static boolean isScanResultForOpenNetwork(ScanResult scanResult) {
return (!(isScanResultForWepNetwork(scanResult) || isScanResultForPskNetwork(scanResult)
|| isScanResultForEapNetwork(scanResult) || isScanResultForSaeNetwork(scanResult)
|| isScanResultForWpa3EnterpriseTransitionNetwork(scanResult)
|| isScanResultForWpa3EnterpriseOnlyNetwork(scanResult)
|| isScanResultForWapiPskNetwork(scanResult)
|| isScanResultForWapiCertNetwork(scanResult)
|| isScanResultForEapSuiteBNetwork(scanResult)
|| isScanResultForUnknownAkmNetwork(scanResult)));
}
/**
* Get InetAddress masked with prefixLength. Will never return null.
* @param address the IP address to mask with
* @param prefixLength the prefixLength used to mask the IP
*/
public static InetAddress getNetworkPart(InetAddress address, int prefixLength) {
byte[] array = address.getAddress();
maskRawAddress(array, prefixLength);
InetAddress netPart = null;
try {
netPart = InetAddress.getByAddress(array);
} catch (UnknownHostException e) {
throw new RuntimeException("getNetworkPart error - " + e.toString());
}
return netPart;
}
/**
* Masks a raw IP address byte array with the specified prefix length.
*/
public static void maskRawAddress(byte[] array, int prefixLength) {
if (prefixLength < 0 || prefixLength > array.length * 8) {
throw new RuntimeException("IP address with " + array.length
+ " bytes has invalid prefix length " + prefixLength);
}
int offset = prefixLength / 8;
int remainder = prefixLength % 8;
byte mask = (byte) (0xFF << (8 - remainder));
if (offset < array.length) array[offset] = (byte) (array[offset] & mask);
offset++;
for (; offset < array.length; offset++) {
array[offset] = 0;
}
}
@Nullable
private static Context createPackageContextAsUser(int uid, Context context) {
Context userContext = null;
try {
userContext = context.createPackageContextAsUser(context.getPackageName(), 0,
UserHandle.getUserHandleForUid(uid));
} catch (PackageManager.NameNotFoundException e) {
return null;
}
return userContext;
}
@Nullable
private static DevicePolicyManager retrieveDevicePolicyManagerFromUserContext(int uid,
Context context) {
Context userContext = createPackageContextAsUser(uid, context);
if (userContext == null) return null;
return userContext.getSystemService(DevicePolicyManager.class);
}
@Nullable
private static Pair<UserHandle, ComponentName> getDeviceOwner(Context context) {
DevicePolicyManager devicePolicyManager =
context.getSystemService(DevicePolicyManager.class);
if (devicePolicyManager == null) return null;
UserHandle deviceOwnerUser = null;
ComponentName deviceOwnerComponent = null;
try {
deviceOwnerUser = devicePolicyManager.getDeviceOwnerUser();
deviceOwnerComponent = devicePolicyManager.getDeviceOwnerComponentOnAnyUser();
} catch (Exception e) {
throw new RuntimeException("getDeviceOwner error - " + e.toString());
}
if (deviceOwnerUser == null || deviceOwnerComponent == null) return null;
if (deviceOwnerComponent.getPackageName() == null) {
// shouldn't happen
return null;
}
return new Pair<>(deviceOwnerUser, deviceOwnerComponent);
}
/**
* Returns true if the |callingUid|/|callingPackage| is the device owner.
*/
public static boolean isDeviceOwner(int uid, @Nullable String packageName, Context context) {
// Cannot determine if the app is DO/PO if packageName is null. So, will return false to be
// safe.
if (packageName == null) {
return false;
}
Pair<UserHandle, ComponentName> deviceOwner = getDeviceOwner(context);
// no device owner
if (deviceOwner == null) return false;
return deviceOwner.first.equals(UserHandle.getUserHandleForUid(uid))
&& deviceOwner.second.getPackageName().equals(packageName);
}
/**
* Returns true if the |callingUid|/|callingPackage| is the profile owner.
*/
public static boolean isProfileOwner(int uid, @Nullable String packageName, Context context) {
// Cannot determine if the app is DO/PO if packageName is null. So, will return false to be
// safe.
if (packageName == null) {
return false;
}
DevicePolicyManager devicePolicyManager =
retrieveDevicePolicyManagerFromUserContext(uid, context);
if (devicePolicyManager == null) return false;
return devicePolicyManager.isProfileOwnerApp(packageName);
}
/**
* Returns true if the |callingUid|/|callingPackage| is the device or profile owner.
*/
public static boolean isDeviceOrProfileOwner(int uid, String packageName, Context context) {
return isDeviceOwner(uid, packageName, context)
|| isProfileOwner(uid, packageName, context);
}
/**
* Unknown security type that cannot be converted to
* DevicePolicyManager.WifiSecurity security type.
*/
public static final int DPM_SECURITY_TYPE_UNKNOWN = -1;
/**
* Utility method to convert WifiInfo.SecurityType to DevicePolicyManager.WifiSecurity
* @param securityType WifiInfo.SecurityType to convert
* @return DevicePolicyManager.WifiSecurity security level, or
* {@link WifiInfo#DPM_SECURITY_TYPE_UNKNOWN} for unknown security types
*/
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
public static int convertSecurityTypeToDpmWifiSecurity(int securityType) {
switch (securityType) {
case WifiInfo.SECURITY_TYPE_OPEN:
case WifiInfo.SECURITY_TYPE_OWE:
return DevicePolicyManager.WIFI_SECURITY_OPEN;
case WifiInfo.SECURITY_TYPE_WEP:
case WifiInfo.SECURITY_TYPE_PSK:
case WifiInfo.SECURITY_TYPE_SAE:
case WifiInfo.SECURITY_TYPE_WAPI_PSK:
return DevicePolicyManager.WIFI_SECURITY_PERSONAL;
case WifiInfo.SECURITY_TYPE_EAP:
case WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE:
case WifiInfo.SECURITY_TYPE_PASSPOINT_R1_R2:
case WifiInfo.SECURITY_TYPE_PASSPOINT_R3:
case WifiInfo.SECURITY_TYPE_WAPI_CERT:
return DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_EAP;
case WifiInfo.SECURITY_TYPE_EAP_WPA3_ENTERPRISE_192_BIT:
return DevicePolicyManager.WIFI_SECURITY_ENTERPRISE_192;
default:
return DPM_SECURITY_TYPE_UNKNOWN;
}
}
/**
* Converts a ScanResult.WIFI_STANDARD_ value to a display string if available, or an
* empty string if there is no corresponding display string.
*/
public static String getStandardString(@NonNull Context context, int standard) {
switch (standard) {
case ScanResult.WIFI_STANDARD_LEGACY:
return context.getString(R.string.wifitrackerlib_wifi_standard_legacy);
case ScanResult.WIFI_STANDARD_11N:
return context.getString(R.string.wifitrackerlib_wifi_standard_11n);
case ScanResult.WIFI_STANDARD_11AC:
return context.getString(R.string.wifitrackerlib_wifi_standard_11ac);
case ScanResult.WIFI_STANDARD_11AX:
return context.getString(R.string.wifitrackerlib_wifi_standard_11ax);
case ScanResult.WIFI_STANDARD_11AD:
return context.getString(R.string.wifitrackerlib_wifi_standard_11ad);
case ScanResult.WIFI_STANDARD_11BE:
return context.getString(R.string.wifitrackerlib_wifi_standard_11be);
default:
return context.getString(R.string.wifitrackerlib_wifi_standard_unknown);
}
}
}