| /* |
| * 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.DISABLED_AUTHENTICATION_FAILURE; |
| import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.DISABLED_AUTHENTICATION_NO_CREDENTIALS; |
| import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD; |
| import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED; |
| import static android.net.wifi.WifiInfo.DEFAULT_MAC_ADDRESS; |
| import static android.net.wifi.WifiInfo.sanitizeSsid; |
| |
| import static androidx.core.util.Preconditions.checkNotNull; |
| |
| import static com.android.wifitrackerlib.Utils.getAppLabel; |
| import static com.android.wifitrackerlib.Utils.getAutoConnectDescription; |
| import static com.android.wifitrackerlib.Utils.getAverageSpeedFromScanResults; |
| import static com.android.wifitrackerlib.Utils.getBestScanResultByLevel; |
| import static com.android.wifitrackerlib.Utils.getCarrierNameForSubId; |
| import static com.android.wifitrackerlib.Utils.getCurrentNetworkCapabilitiesInformation; |
| import static com.android.wifitrackerlib.Utils.getDisconnectedStateDescription; |
| import static com.android.wifitrackerlib.Utils.getImsiProtectionDescription; |
| import static com.android.wifitrackerlib.Utils.getMeteredDescription; |
| import static com.android.wifitrackerlib.Utils.getNetworkDetailedState; |
| import static com.android.wifitrackerlib.Utils.getSecurityTypeFromWifiConfiguration; |
| import static com.android.wifitrackerlib.Utils.getSpeedDescription; |
| import static com.android.wifitrackerlib.Utils.getSpeedFromWifiInfo; |
| import static com.android.wifitrackerlib.Utils.getSubIdForConfig; |
| import static com.android.wifitrackerlib.Utils.getVerboseLoggingDescription; |
| |
| import android.content.Context; |
| import android.net.ConnectivityManager; |
| import android.net.NetworkCapabilities; |
| import android.net.NetworkInfo; |
| import android.net.NetworkScoreManager; |
| import android.net.NetworkScorerAppData; |
| import android.net.wifi.ScanResult; |
| import android.net.wifi.WifiConfiguration; |
| import android.net.wifi.WifiConfiguration.NetworkSelectionStatus; |
| import android.net.wifi.WifiInfo; |
| import android.net.wifi.WifiManager; |
| import android.net.wifi.WifiNetworkScoreCache; |
| import android.os.Handler; |
| import android.os.SystemClock; |
| import android.telephony.SubscriptionInfo; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.TelephonyManager; |
| import android.text.TextUtils; |
| |
| import androidx.annotation.GuardedBy; |
| import androidx.annotation.IntDef; |
| import androidx.annotation.NonNull; |
| import androidx.annotation.Nullable; |
| import androidx.annotation.WorkerThread; |
| |
| import com.android.internal.annotations.VisibleForTesting; |
| |
| import java.lang.annotation.Retention; |
| import java.lang.annotation.RetentionPolicy; |
| import java.util.ArrayList; |
| import java.util.Comparator; |
| import java.util.List; |
| import java.util.StringJoiner; |
| import java.util.stream.Collectors; |
| |
| /** |
| * WifiEntry representation of a logical Wi-Fi network, uniquely identified by SSID and security. |
| * |
| * This type of WifiEntry can represent both open and saved networks. |
| */ |
| @VisibleForTesting |
| public class StandardWifiEntry extends WifiEntry { |
| static final String KEY_PREFIX = "StandardWifiEntry:"; |
| |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef(value = { |
| EAP_WPA, |
| EAP_WPA2_WPA3, |
| EAP_UNKNOWN |
| }) |
| |
| public @interface EapType {} |
| |
| private static final int EAP_WPA = 0; // WPA-EAP |
| private static final int EAP_WPA2_WPA3 = 1; // RSN-EAP |
| private static final int EAP_UNKNOWN = 2; |
| |
| @Retention(RetentionPolicy.SOURCE) |
| @IntDef(value = { |
| PSK_WPA, |
| PSK_WPA2, |
| PSK_WPA_WPA2, |
| PSK_UNKNOWN |
| }) |
| |
| public @interface PskType {} |
| |
| private static final int PSK_WPA = 0; |
| private static final int PSK_WPA2 = 1; |
| private static final int PSK_WPA_WPA2 = 2; |
| private static final int PSK_UNKNOWN = 3; |
| |
| private final Object mLock = new Object(); |
| // Scan result list must be thread safe for generating the verbose scan summary |
| @GuardedBy("mLock") |
| @NonNull private final List<ScanResult> mCurrentScanResults = new ArrayList<>(); |
| |
| @NonNull private final String mKey; |
| @NonNull private final String mSsid; |
| @NonNull private final Context mContext; |
| private final @Security int mSecurity; |
| private @EapType int mEapType = EAP_UNKNOWN; |
| private @PskType int mPskType = PSK_UNKNOWN; |
| @Nullable private WifiConfiguration mWifiConfig; |
| private boolean mIsUserShareable = false; |
| @Nullable private String mRecommendationServiceLabel; |
| |
| private boolean mShouldAutoOpenCaptivePortal = false; |
| |
| StandardWifiEntry(@NonNull Context context, @NonNull Handler callbackHandler, |
| @NonNull String key, |
| @NonNull List<ScanResult> scanResults, |
| @NonNull WifiManager wifiManager, |
| @NonNull WifiNetworkScoreCache scoreCache, |
| boolean forSavedNetworksPage) throws IllegalArgumentException { |
| this(context, callbackHandler, key, wifiManager, scoreCache, |
| forSavedNetworksPage); |
| |
| checkNotNull(scanResults, "Cannot construct with null ScanResult list!"); |
| if (scanResults.isEmpty()) { |
| throw new IllegalArgumentException("Cannot construct with empty ScanResult list!"); |
| } |
| updateScanResultInfo(scanResults); |
| updateRecommendationServiceLabel(); |
| } |
| |
| StandardWifiEntry(@NonNull Context context, @NonNull Handler callbackHandler, |
| @NonNull String key, @NonNull WifiConfiguration config, |
| @NonNull WifiManager wifiManager, |
| @NonNull WifiNetworkScoreCache scoreCache, |
| boolean forSavedNetworksPage) throws IllegalArgumentException { |
| this(context, callbackHandler, key, wifiManager, scoreCache, |
| forSavedNetworksPage); |
| |
| checkNotNull(config, "Cannot construct with null config!"); |
| checkNotNull(config.SSID, "Supplied config must have an SSID!"); |
| mWifiConfig = config; |
| updateRecommendationServiceLabel(); |
| } |
| |
| StandardWifiEntry(@NonNull Context context, @NonNull Handler callbackHandler, |
| @NonNull String key, @NonNull WifiManager wifiManager, |
| @NonNull WifiNetworkScoreCache scoreCache, |
| boolean forSavedNetworksPage) { |
| super(callbackHandler, wifiManager, scoreCache, forSavedNetworksPage); |
| |
| mContext = context; |
| mKey = key; |
| try { |
| final int prefixDelimiter = key.indexOf(":"); |
| final int securityDelimiter = key.lastIndexOf(","); |
| mSsid = key.substring(prefixDelimiter + 1, securityDelimiter); |
| mSecurity = Integer.valueOf(key.substring(securityDelimiter + 1)); |
| } catch (StringIndexOutOfBoundsException | NumberFormatException e) { |
| throw new IllegalArgumentException("Malformed key: " + key); |
| } |
| updateRecommendationServiceLabel(); |
| } |
| |
| @Override |
| public String getKey() { |
| return mKey; |
| } |
| |
| @Override |
| public String getTitle() { |
| return mSsid; |
| } |
| |
| @Override |
| public String getSummary(boolean concise) { |
| StringJoiner sj = new StringJoiner(mContext.getString(R.string.summary_separator)); |
| |
| if (!concise && mForSavedNetworksPage && isSaved()) { |
| final CharSequence appLabel = getAppLabel(mContext, mWifiConfig.creatorName); |
| if (!TextUtils.isEmpty(appLabel)) { |
| sj.add(mContext.getString(R.string.saved_network, appLabel)); |
| } |
| } |
| |
| if (getConnectedState() == CONNECTED_STATE_DISCONNECTED) { |
| String disconnectDescription = getDisconnectedStateDescription(mContext, this); |
| if (TextUtils.isEmpty(disconnectDescription)) { |
| if (concise) { |
| sj.add(mContext.getString(R.string.wifi_disconnected)); |
| } else if (!mForSavedNetworksPage) { |
| // Summary for unconnected suggested network |
| if (isSuggestion()) { |
| String carrierName = getCarrierNameForSubId(mContext, |
| getSubIdForConfig(mContext, mWifiConfig)); |
| String suggestorName = getAppLabel(mContext, mWifiConfig.creatorName); |
| if (TextUtils.isEmpty(suggestorName)) { |
| // Fall-back to the package name in case the app label is missing |
| suggestorName = mWifiConfig.creatorName; |
| } |
| sj.add(mContext.getString(R.string.available_via_app, carrierName != null |
| ? carrierName : suggestorName)); |
| } else if (isSaved()) { |
| sj.add(mContext.getString(R.string.wifi_remembered)); |
| } |
| } |
| } else { |
| sj.add(disconnectDescription); |
| } |
| } else { |
| final String connectDescription = getConnectStateDescription(); |
| if (!TextUtils.isEmpty(connectDescription)) { |
| sj.add(connectDescription); |
| } |
| } |
| |
| final String speedDescription = getSpeedDescription(mContext, this); |
| if (!TextUtils.isEmpty(speedDescription)) { |
| sj.add(speedDescription); |
| } |
| |
| final String autoConnectDescription = getAutoConnectDescription(mContext, this); |
| if (!TextUtils.isEmpty(autoConnectDescription)) { |
| sj.add(autoConnectDescription); |
| } |
| |
| final String meteredDescription = getMeteredDescription(mContext, this); |
| if (!TextUtils.isEmpty(meteredDescription)) { |
| sj.add(meteredDescription); |
| } |
| |
| if (!concise) { |
| final String verboseLoggingDescription = getVerboseLoggingDescription(this); |
| if (!TextUtils.isEmpty(verboseLoggingDescription)) { |
| sj.add(verboseLoggingDescription); |
| } |
| } |
| |
| return sj.toString(); |
| } |
| |
| private String getConnectStateDescription() { |
| if (getConnectedState() == CONNECTED_STATE_CONNECTED) { |
| // For suggestion or specifier networks |
| final String suggestionOrSpecifierPackageName = mWifiInfo != null |
| ? mWifiInfo.getRequestingPackageName() : null; |
| if (!TextUtils.isEmpty(suggestionOrSpecifierPackageName)) { |
| String carrierName = mWifiConfig != null |
| ? getCarrierNameForSubId(mContext, getSubIdForConfig(mContext, mWifiConfig)) |
| : null; |
| String suggestorName = getAppLabel(mContext, suggestionOrSpecifierPackageName); |
| if (TextUtils.isEmpty(suggestorName)) { |
| // Fall-back to the package name in case the app label is missing |
| suggestorName = suggestionOrSpecifierPackageName; |
| } |
| return mContext.getString(R.string.connected_via_app, carrierName != null |
| ? carrierName : suggestorName); |
| } |
| |
| if (!isSaved() && !isSuggestion()) { |
| // Special case for connected + ephemeral networks. |
| if (!TextUtils.isEmpty(mRecommendationServiceLabel)) { |
| return String.format(mContext.getString(R.string.connected_via_network_scorer), |
| mRecommendationServiceLabel); |
| } |
| return mContext.getString(R.string.connected_via_network_scorer_default); |
| } |
| |
| String networkCapabilitiesinformation = |
| getCurrentNetworkCapabilitiesInformation(mContext, mNetworkCapabilities); |
| if (!TextUtils.isEmpty(networkCapabilitiesinformation)) { |
| return networkCapabilitiesinformation; |
| } |
| } |
| |
| return getNetworkDetailedState(mContext, mNetworkInfo); |
| } |
| |
| @Override |
| public CharSequence getSecondSummary() { |
| return getConnectedState() == CONNECTED_STATE_CONNECTED |
| ? getImsiProtectionDescription(mContext, getWifiConfiguration()) : ""; |
| } |
| |
| @Override |
| public String getSsid() { |
| return mSsid; |
| } |
| |
| @Override |
| @Security |
| public int getSecurity() { |
| return mSecurity; |
| } |
| |
| @Override |
| public String getMacAddress() { |
| if (mWifiInfo != null) { |
| final String wifiInfoMac = mWifiInfo.getMacAddress(); |
| if (!TextUtils.isEmpty(wifiInfoMac) |
| && !TextUtils.equals(wifiInfoMac, DEFAULT_MAC_ADDRESS)) { |
| return wifiInfoMac; |
| } |
| } |
| if (mWifiConfig == null || getPrivacy() != PRIVACY_RANDOMIZED_MAC) { |
| final String[] factoryMacs = mWifiManager.getFactoryMacAddresses(); |
| if (factoryMacs.length > 0) { |
| return factoryMacs[0]; |
| } |
| return null; |
| } |
| return mWifiConfig.getRandomizedMacAddress().toString(); |
| } |
| |
| @Override |
| public boolean isMetered() { |
| return getMeteredChoice() == METERED_CHOICE_METERED |
| || (mWifiConfig != null && mWifiConfig.meteredHint); |
| } |
| |
| @Override |
| public boolean isSaved() { |
| return mWifiConfig != null && !mWifiConfig.isEphemeral(); |
| } |
| |
| @Override |
| public boolean isSuggestion() { |
| return mWifiConfig != null && mWifiConfig.fromWifiNetworkSuggestion; |
| } |
| |
| @Override |
| public boolean isSubscription() { |
| return false; |
| } |
| |
| @Override |
| public WifiConfiguration getWifiConfiguration() { |
| if (!isSaved()) { |
| return null; |
| } |
| return mWifiConfig; |
| } |
| |
| @Override |
| public ConnectedInfo getConnectedInfo() { |
| return mConnectedInfo; |
| } |
| |
| @Override |
| public boolean canConnect() { |
| if (mLevel == WIFI_LEVEL_UNREACHABLE |
| || getConnectedState() != CONNECTED_STATE_DISCONNECTED) { |
| return false; |
| } |
| // Allow connection for EAP SIM dependent methods if the SIM of specified carrier ID is |
| // active in the device. |
| if (getSecurity() == SECURITY_EAP && mWifiConfig != null |
| && mWifiConfig.enterpriseConfig != null) { |
| if (!mWifiConfig.enterpriseConfig.isAuthenticationSimBased()) { |
| return true; |
| } |
| List<SubscriptionInfo> activeSubscriptionInfos = ((SubscriptionManager) mContext |
| .getSystemService(Context.TELEPHONY_SUBSCRIPTION_SERVICE)) |
| .getActiveSubscriptionInfoList(); |
| if (activeSubscriptionInfos == null || activeSubscriptionInfos.size() == 0) { |
| return false; |
| } |
| if (mWifiConfig.carrierId == TelephonyManager.UNKNOWN_CARRIER_ID) { |
| // To connect via default subscription. |
| return true; |
| } |
| for (SubscriptionInfo subscriptionInfo : activeSubscriptionInfos) { |
| if (subscriptionInfo.getCarrierId() == mWifiConfig.carrierId) { |
| return true; |
| } |
| } |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public void connect(@Nullable ConnectCallback callback) { |
| mConnectCallback = callback; |
| // We should flag this network to auto-open captive portal since this method represents |
| // the user manually connecting to a network (i.e. not auto-join). |
| mShouldAutoOpenCaptivePortal = true; |
| |
| if (isSaved() || isSuggestion()) { |
| // Saved/suggested network |
| mWifiManager.connect(mWifiConfig.networkId, new ConnectActionListener()); |
| } else { |
| // Unsaved network |
| if (mSecurity == SECURITY_NONE |
| || mSecurity == SECURITY_OWE) { |
| // Open network |
| final WifiConfiguration connectConfig = new WifiConfiguration(); |
| connectConfig.SSID = "\"" + mSsid + "\""; |
| |
| if (mSecurity == SECURITY_OWE) { |
| // Use OWE if possible |
| connectConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.OWE); |
| connectConfig.requirePmf = true; |
| } else { |
| connectConfig.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); |
| } |
| mWifiManager.connect(connectConfig, new ConnectActionListener()); |
| } else { |
| // Secure network |
| if (callback != null) { |
| mCallbackHandler.post(() -> |
| callback.onConnectResult( |
| ConnectCallback.CONNECT_STATUS_FAILURE_NO_CONFIG)); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public boolean canDisconnect() { |
| return getConnectedState() == CONNECTED_STATE_CONNECTED; |
| } |
| |
| @Override |
| public void disconnect(@Nullable DisconnectCallback callback) { |
| if (canDisconnect()) { |
| mCalledDisconnect = true; |
| mDisconnectCallback = callback; |
| mCallbackHandler.postDelayed(() -> { |
| if (callback != null && mCalledDisconnect) { |
| callback.onDisconnectResult( |
| DisconnectCallback.DISCONNECT_STATUS_FAILURE_UNKNOWN); |
| } |
| }, 10_000 /* delayMillis */); |
| mWifiManager.disableEphemeralNetwork(mWifiConfig.SSID); |
| mWifiManager.disconnect(); |
| } |
| } |
| |
| @Override |
| public boolean canForget() { |
| return getWifiConfiguration() != null; |
| } |
| |
| @Override |
| public void forget(@Nullable ForgetCallback callback) { |
| if (canForget()) { |
| mForgetCallback = callback; |
| mWifiManager.forget(mWifiConfig.networkId, new ForgetActionListener()); |
| } |
| } |
| |
| @Override |
| public boolean canSignIn() { |
| return mNetworkCapabilities != null |
| && mNetworkCapabilities.hasCapability( |
| NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL); |
| } |
| |
| @Override |
| public void signIn(@Nullable SignInCallback callback) { |
| if (canSignIn()) { |
| // canSignIn() implies that this WifiEntry is the currently connected network, so use |
| // getCurrentNetwork() to start the captive portal app. |
| ((ConnectivityManager) mContext.getSystemService(Context.CONNECTIVITY_SERVICE)) |
| .startCaptivePortalApp(mWifiManager.getCurrentNetwork()); |
| } |
| } |
| |
| /** |
| * Returns whether the network can be shared via QR code. |
| * See https://github.com/zxing/zxing/wiki/Barcode-Contents#wi-fi-network-config-android-ios-11 |
| */ |
| @Override |
| public boolean canShare() { |
| if (getWifiConfiguration() == null) { |
| return false; |
| } |
| |
| switch (mSecurity) { |
| case SECURITY_NONE: |
| case SECURITY_OWE: |
| case SECURITY_WEP: |
| case SECURITY_PSK: |
| case SECURITY_SAE: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| /** |
| * Returns whether the user can use Easy Connect to onboard a device to the network. |
| * See https://www.wi-fi.org/discover-wi-fi/wi-fi-easy-connect |
| */ |
| @Override |
| public boolean canEasyConnect() { |
| if (getWifiConfiguration() == null) { |
| return false; |
| } |
| |
| if (!mWifiManager.isEasyConnectSupported()) { |
| return false; |
| } |
| |
| // DPP 1.0 only supports WPA2 and WPA3. |
| switch (mSecurity) { |
| case SECURITY_PSK: |
| case SECURITY_SAE: |
| return true; |
| default: |
| return false; |
| } |
| } |
| |
| @Override |
| @MeteredChoice |
| public int getMeteredChoice() { |
| if (getWifiConfiguration() != null) { |
| final int meteredOverride = getWifiConfiguration().meteredOverride; |
| if (meteredOverride == WifiConfiguration.METERED_OVERRIDE_METERED) { |
| return METERED_CHOICE_METERED; |
| } else if (meteredOverride == WifiConfiguration.METERED_OVERRIDE_NOT_METERED) { |
| return METERED_CHOICE_UNMETERED; |
| } |
| } |
| return METERED_CHOICE_AUTO; |
| } |
| |
| @Override |
| public boolean canSetMeteredChoice() { |
| return getWifiConfiguration() != null; |
| } |
| |
| @Override |
| public void setMeteredChoice(int meteredChoice) { |
| if (!canSetMeteredChoice()) { |
| return; |
| } |
| |
| if (meteredChoice == METERED_CHOICE_AUTO) { |
| mWifiConfig.meteredOverride = WifiConfiguration.METERED_OVERRIDE_NONE; |
| } else if (meteredChoice == METERED_CHOICE_METERED) { |
| mWifiConfig.meteredOverride = WifiConfiguration.METERED_OVERRIDE_METERED; |
| } else if (meteredChoice == METERED_CHOICE_UNMETERED) { |
| mWifiConfig.meteredOverride = WifiConfiguration.METERED_OVERRIDE_NOT_METERED; |
| } |
| mWifiManager.save(mWifiConfig, null /* listener */); |
| } |
| |
| @Override |
| public boolean canSetPrivacy() { |
| return isSaved(); |
| } |
| |
| @Override |
| @Privacy |
| public int getPrivacy() { |
| if (mWifiConfig != null |
| && mWifiConfig.macRandomizationSetting == WifiConfiguration.RANDOMIZATION_NONE) { |
| return PRIVACY_DEVICE_MAC; |
| } else { |
| return PRIVACY_RANDOMIZED_MAC; |
| } |
| } |
| |
| @Override |
| public void setPrivacy(int privacy) { |
| if (!canSetPrivacy()) { |
| return; |
| } |
| |
| mWifiConfig.macRandomizationSetting = privacy == PRIVACY_RANDOMIZED_MAC |
| ? WifiConfiguration.RANDOMIZATION_PERSISTENT : WifiConfiguration.RANDOMIZATION_NONE; |
| mWifiManager.save(mWifiConfig, null /* listener */); |
| } |
| |
| @Override |
| public boolean isAutoJoinEnabled() { |
| if (mWifiConfig == null) { |
| return false; |
| } |
| |
| return mWifiConfig.allowAutojoin; |
| } |
| |
| @Override |
| public boolean canSetAutoJoinEnabled() { |
| return isSaved() || isSuggestion(); |
| } |
| |
| @Override |
| public void setAutoJoinEnabled(boolean enabled) { |
| if (!canSetAutoJoinEnabled()) { |
| return; |
| } |
| |
| mWifiManager.allowAutojoin(mWifiConfig.networkId, enabled); |
| } |
| |
| @Override |
| public String getSecurityString(boolean concise) { |
| switch(mSecurity) { |
| case SECURITY_EAP: |
| switch (mEapType) { |
| case EAP_WPA: |
| return concise ? mContext.getString(R.string.wifi_security_short_eap_wpa) : |
| mContext.getString(R.string.wifi_security_eap_wpa); |
| case EAP_WPA2_WPA3: |
| return concise |
| ? mContext.getString(R.string.wifi_security_short_eap_wpa2_wpa3) : |
| mContext.getString(R.string.wifi_security_eap_wpa2_wpa3); |
| case EAP_UNKNOWN: |
| default: |
| return concise ? mContext.getString(R.string.wifi_security_short_eap) : |
| mContext.getString(R.string.wifi_security_eap); |
| } |
| case SECURITY_EAP_SUITE_B: |
| return concise ? mContext.getString(R.string.wifi_security_short_eap_suiteb) : |
| mContext.getString(R.string.wifi_security_eap_suiteb); |
| case SECURITY_PSK: |
| switch (mPskType) { |
| case PSK_WPA: |
| return concise ? mContext.getString(R.string.wifi_security_short_wpa) : |
| mContext.getString(R.string.wifi_security_wpa); |
| case PSK_WPA2: |
| return concise |
| ? mContext.getString(R.string.wifi_security_short_wpa2_wpa3) : |
| mContext.getString(R.string.wifi_security_wpa2_wpa3); |
| case PSK_WPA_WPA2: |
| case PSK_UNKNOWN: |
| default: |
| return concise |
| ? mContext.getString(R.string.wifi_security_short_wpa_wpa2_wpa3) : |
| mContext.getString(R.string.wifi_security_wpa_wpa2_wpa3); |
| } |
| case SECURITY_WEP: |
| return mContext.getString(R.string.wifi_security_wep); |
| case SECURITY_SAE: |
| return concise ? mContext.getString(R.string.wifi_security_short_sae) : |
| mContext.getString(R.string.wifi_security_sae); |
| case SECURITY_OWE: |
| return concise ? mContext.getString(R.string.wifi_security_short_owe) : |
| mContext.getString(R.string.wifi_security_owe); |
| case SECURITY_NONE: |
| default: |
| return concise ? "" : mContext.getString(R.string.wifi_security_none); |
| } |
| } |
| |
| @Override |
| public boolean isExpired() { |
| return false; |
| } |
| |
| @Override |
| public boolean shouldEditBeforeConnect() { |
| WifiConfiguration wifiConfig = getWifiConfiguration(); |
| if (wifiConfig == null) { |
| return false; |
| } |
| |
| // The secured Wi-Fi entry is never connected. |
| if (getSecurity() != SECURITY_NONE && getSecurity() != SECURITY_OWE |
| && !wifiConfig.getNetworkSelectionStatus().hasEverConnected()) { |
| return true; |
| } |
| |
| // The network is disabled because of one of the authentication problems. |
| NetworkSelectionStatus networkSelectionStatus = wifiConfig.getNetworkSelectionStatus(); |
| if (networkSelectionStatus.getNetworkSelectionStatus() != NETWORK_SELECTION_ENABLED) { |
| if (networkSelectionStatus.getDisableReasonCounter(DISABLED_AUTHENTICATION_FAILURE) > 0 |
| || networkSelectionStatus.getDisableReasonCounter( |
| DISABLED_BY_WRONG_PASSWORD) > 0 |
| || networkSelectionStatus.getDisableReasonCounter( |
| DISABLED_AUTHENTICATION_NO_CREDENTIALS) > 0) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| @WorkerThread |
| void updateScanResultInfo(@Nullable List<ScanResult> scanResults) |
| throws IllegalArgumentException { |
| if (scanResults == null) scanResults = new ArrayList<>(); |
| |
| for (ScanResult result : scanResults) { |
| if (!TextUtils.equals(result.SSID, mSsid)) { |
| throw new IllegalArgumentException( |
| "Attempted to update with wrong SSID! Expected: " |
| + mSsid + ", Actual: " + result.SSID + ", ScanResult: " + result); |
| } |
| } |
| |
| synchronized (mLock) { |
| mCurrentScanResults.clear(); |
| mCurrentScanResults.addAll(scanResults); |
| } |
| |
| final ScanResult bestScanResult = getBestScanResultByLevel(scanResults); |
| if (bestScanResult != null) { |
| updateEapType(bestScanResult); |
| updatePskType(bestScanResult); |
| } |
| |
| if (getConnectedState() == CONNECTED_STATE_DISCONNECTED) { |
| mLevel = bestScanResult != null |
| ? mWifiManager.calculateSignalLevel(bestScanResult.level) |
| : WIFI_LEVEL_UNREACHABLE; |
| synchronized (mLock) { |
| // Average speed is used to prevent speed label flickering from multiple APs. |
| mSpeed = getAverageSpeedFromScanResults(mScoreCache, mCurrentScanResults); |
| } |
| } |
| notifyOnUpdated(); |
| } |
| |
| @WorkerThread |
| @Override |
| void updateNetworkCapabilities(@Nullable NetworkCapabilities capabilities) { |
| super.updateNetworkCapabilities(capabilities); |
| |
| // Auto-open an available captive portal if the user manually connected to this network. |
| if (canSignIn() && mShouldAutoOpenCaptivePortal) { |
| mShouldAutoOpenCaptivePortal = false; |
| signIn(null /* callback */); |
| } |
| } |
| |
| @WorkerThread |
| void onScoreCacheUpdated() { |
| if (mWifiInfo != null) { |
| mSpeed = getSpeedFromWifiInfo(mScoreCache, mWifiInfo); |
| } else { |
| synchronized (mLock) { |
| // Average speed is used to prevent speed label flickering from multiple APs. |
| mSpeed = getAverageSpeedFromScanResults(mScoreCache, mCurrentScanResults); |
| } |
| } |
| notifyOnUpdated(); |
| } |
| |
| private void updateEapType(ScanResult result) { |
| if (result.capabilities.contains("RSN-EAP")) { |
| // WPA2-Enterprise and WPA3-Enterprise (non 192-bit) advertise RSN-EAP-CCMP |
| mEapType = EAP_WPA2_WPA3; |
| } else if (result.capabilities.contains("WPA-EAP")) { |
| // WPA-Enterprise advertises WPA-EAP-TKIP |
| mEapType = EAP_WPA; |
| } else { |
| mEapType = EAP_UNKNOWN; |
| } |
| } |
| |
| private void updatePskType(ScanResult result) { |
| if (mSecurity != SECURITY_PSK) { |
| mPskType = PSK_UNKNOWN; |
| return; |
| } |
| |
| final boolean wpa = result.capabilities.contains("WPA-PSK"); |
| final boolean wpa2 = result.capabilities.contains("RSN-PSK"); |
| if (wpa2 && wpa) { |
| mPskType = PSK_WPA_WPA2; |
| } else if (wpa2) { |
| mPskType = PSK_WPA2; |
| } else if (wpa) { |
| mPskType = PSK_WPA; |
| } else { |
| mPskType = PSK_UNKNOWN; |
| } |
| } |
| |
| @WorkerThread |
| void updateConfig(@Nullable WifiConfiguration wifiConfig) throws IllegalArgumentException { |
| if (wifiConfig != null) { |
| if (!TextUtils.equals(mSsid, sanitizeSsid(wifiConfig.SSID))) { |
| throw new IllegalArgumentException( |
| "Attempted to update with wrong SSID!" |
| + " Expected: " + mSsid |
| + ", Actual: " + sanitizeSsid(wifiConfig.SSID) |
| + ", Config: " + wifiConfig); |
| } |
| if (mSecurity != getSecurityTypeFromWifiConfiguration(wifiConfig)) { |
| throw new IllegalArgumentException( |
| "Attempted to update with wrong security!" |
| + " Expected: " + mSecurity |
| + ", Actual: " + getSecurityTypeFromWifiConfiguration(wifiConfig) |
| + ", Config: " + wifiConfig); |
| } |
| } |
| |
| mWifiConfig = wifiConfig; |
| notifyOnUpdated(); |
| } |
| |
| /** |
| * Sets whether the suggested config for this entry is shareable to the user or not. |
| */ |
| @WorkerThread |
| void setUserShareable(boolean isUserShareable) { |
| mIsUserShareable = isUserShareable; |
| } |
| |
| /** |
| * Returns whether the suggested config for this entry is shareable to the user or not. |
| */ |
| @WorkerThread |
| boolean isUserShareable() { |
| return mIsUserShareable; |
| } |
| |
| @WorkerThread |
| protected boolean connectionInfoMatches(@NonNull WifiInfo wifiInfo, |
| @NonNull NetworkInfo networkInfo) { |
| if (wifiInfo.isPasspointAp() || wifiInfo.isOsuAp()) { |
| return false; |
| } |
| |
| if (mWifiConfig != null) { |
| if (mWifiConfig.networkId == wifiInfo.getNetworkId()) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private void updateRecommendationServiceLabel() { |
| final NetworkScorerAppData scorer = ((NetworkScoreManager) mContext |
| .getSystemService(Context.NETWORK_SCORE_SERVICE)).getActiveScorer(); |
| if (scorer != null) { |
| mRecommendationServiceLabel = scorer.getRecommendationServiceLabel(); |
| } |
| } |
| |
| @NonNull |
| static String ssidAndSecurityToStandardWifiEntryKey(@NonNull String ssid, |
| @Security int security) { |
| return KEY_PREFIX + ssid + "," + security; |
| } |
| |
| @NonNull |
| static String wifiConfigToStandardWifiEntryKey(@NonNull WifiConfiguration config) { |
| checkNotNull(config, "Cannot create key with null config!"); |
| checkNotNull(config.SSID, "Cannot create key with null SSID in config!"); |
| return KEY_PREFIX + sanitizeSsid(config.SSID) + "," |
| + getSecurityTypeFromWifiConfiguration(config); |
| } |
| |
| @Override |
| String getScanResultDescription() { |
| synchronized (mLock) { |
| if (mCurrentScanResults.size() == 0) { |
| return ""; |
| } |
| } |
| |
| final StringBuilder description = new StringBuilder(); |
| description.append("["); |
| description.append(getScanResultDescription(MIN_FREQ_24GHZ, MAX_FREQ_24GHZ)).append(";"); |
| description.append(getScanResultDescription(MIN_FREQ_5GHZ, MAX_FREQ_5GHZ)).append(";"); |
| description.append(getScanResultDescription(MIN_FREQ_6GHZ, MAX_FREQ_6GHZ)); |
| description.append("]"); |
| return description.toString(); |
| } |
| |
| private String getScanResultDescription(int minFrequency, int maxFrequency) { |
| final List<ScanResult> scanResults; |
| synchronized (mLock) { |
| scanResults = mCurrentScanResults.stream() |
| .filter(scanResult -> scanResult.frequency >= minFrequency |
| && scanResult.frequency <= maxFrequency) |
| .sorted(Comparator.comparingInt(scanResult -> -1 * scanResult.level)) |
| .collect(Collectors.toList()); |
| } |
| |
| final int scanResultCount = scanResults.size(); |
| if (scanResultCount == 0) { |
| return ""; |
| } |
| |
| final StringBuilder description = new StringBuilder(); |
| description.append("(").append(scanResultCount).append(")"); |
| if (scanResultCount > MAX_VERBOSE_LOG_DISPLAY_SCANRESULT_COUNT) { |
| final int maxLavel = scanResults.stream() |
| .mapToInt(scanResult -> scanResult.level).max().getAsInt(); |
| description.append("max=").append(maxLavel).append(","); |
| } |
| final long nowMs = SystemClock.elapsedRealtime(); |
| scanResults.forEach(scanResult -> |
| description.append(getScanResultDescription(scanResult, nowMs))); |
| return description.toString(); |
| } |
| |
| private String getScanResultDescription(ScanResult scanResult, long nowMs) { |
| final StringBuilder description = new StringBuilder(); |
| description.append(" \n{"); |
| description.append(scanResult.BSSID); |
| if (mWifiInfo != null && scanResult.BSSID.equals(mWifiInfo.getBSSID())) { |
| description.append("*"); |
| } |
| description.append("=").append(scanResult.frequency); |
| description.append(",").append(scanResult.level); |
| final int ageSeconds = (int) (nowMs - scanResult.timestamp / 1000) / 1000; |
| description.append(",").append(ageSeconds).append("s"); |
| description.append("}"); |
| return description.toString(); |
| } |
| |
| @Override |
| String getNetworkSelectionDescription() { |
| return Utils.getNetworkSelectionDescription(getWifiConfiguration()); |
| } |
| } |