/* * 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.settings.wifi; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.net.InetAddresses; import android.net.IpConfiguration; import android.net.IpConfiguration.IpAssignment; import android.net.IpConfiguration.ProxySettings; import android.net.LinkAddress; import android.net.ProxyInfo; import android.net.StaticIpConfiguration; import android.net.Uri; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiEnterpriseConfig; import android.net.wifi.WifiEnterpriseConfig.Eap; import android.net.wifi.WifiEnterpriseConfig.Phase2; import android.net.wifi.WifiManager; import android.os.IBinder; import android.security.keystore.KeyProperties; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.text.Editable; import android.text.InputType; import android.text.SpannableString; import android.text.TextUtils; import android.text.TextWatcher; import android.util.Log; import android.view.KeyEvent; import android.view.View; import android.view.View.AccessibilityDelegate; import android.view.ViewGroup; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.CompoundButton.OnCheckedChangeListener; import android.widget.EditText; import android.widget.ImageButton; import android.widget.Spinner; import android.widget.TextView; import androidx.annotation.VisibleForTesting; import com.android.net.module.util.NetUtils; import com.android.net.module.util.ProxyUtils; import com.android.settings.ProxySelector; import com.android.settings.R; import com.android.settings.network.SubscriptionUtil; import com.android.settings.utils.AndroidKeystoreAliasLoader; import com.android.settings.wifi.details2.WifiPrivacyPreferenceController2; import com.android.settings.wifi.dpp.WifiDppUtils; import com.android.settingslib.Utils; import com.android.settingslib.utils.ThreadUtils; import com.android.wifitrackerlib.WifiEntry; import com.android.wifitrackerlib.WifiEntry.ConnectedInfo; import java.net.Inet4Address; import java.net.InetAddress; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.stream.Collectors; /** * The class for allowing UIs like {@link WifiDialog2} and {@link WifiConfigUiBase2} to * share the logic for controlling buttons, text fields, etc. */ public class WifiConfigController2 implements TextWatcher, AdapterView.OnItemSelectedListener, OnCheckedChangeListener, TextView.OnEditorActionListener, View.OnKeyListener { private static final String TAG = "WifiConfigController2"; private static final String SYSTEM_CA_STORE_PATH = "/system/etc/security/cacerts"; private final WifiConfigUiBase2 mConfigUi; private final View mView; private final WifiEntry mWifiEntry; /* This value comes from "wifi_ip_settings" resource array */ private static final int DHCP = 0; private static final int STATIC_IP = 1; /* Constants used for referring to the hidden state of a network. */ public static final int HIDDEN_NETWORK = 1; public static final int NOT_HIDDEN_NETWORK = 0; /* These values come from "wifi_proxy_settings" resource array */ public static final int PROXY_NONE = 0; public static final int PROXY_STATIC = 1; public static final int PROXY_PAC = 2; /* These values come from "wifi_eap_method" resource array */ public static final int WIFI_EAP_METHOD_PEAP = 0; public static final int WIFI_EAP_METHOD_TLS = 1; public static final int WIFI_EAP_METHOD_TTLS = 2; public static final int WIFI_EAP_METHOD_PWD = 3; public static final int WIFI_EAP_METHOD_SIM = 4; public static final int WIFI_EAP_METHOD_AKA = 5; public static final int WIFI_EAP_METHOD_AKA_PRIME = 6; /* These values come from "wifi_peap_phase2_entries" resource array */ public static final int WIFI_PEAP_PHASE2_MSCHAPV2 = 0; public static final int WIFI_PEAP_PHASE2_GTC = 1; public static final int WIFI_PEAP_PHASE2_SIM = 2; public static final int WIFI_PEAP_PHASE2_AKA = 3; public static final int WIFI_PEAP_PHASE2_AKA_PRIME = 4; /* These values come from "wifi_ttls_phase2_entries" resource array */ public static final int WIFI_TTLS_PHASE2_PAP = 0; public static final int WIFI_TTLS_PHASE2_MSCHAP = 1; public static final int WIFI_TTLS_PHASE2_MSCHAPV2 = 2; public static final int WIFI_TTLS_PHASE2_GTC = 3; private static final String UNDESIRED_CERTIFICATE_MACRANDSECRET = "MacRandSecret"; private static final String UNDESIRED_CERTIFICATE_MACRANDSAPSECRET = "MacRandSapSecret"; @VisibleForTesting static final String[] UNDESIRED_CERTIFICATES = { UNDESIRED_CERTIFICATE_MACRANDSECRET, UNDESIRED_CERTIFICATE_MACRANDSAPSECRET }; /* These values are for install certificate */ private static final String ACTION_INSTALL_CERTS = "android.credentials.INSTALL"; private static final String PACKAGE_INSTALL_CERTS = "com.android.certinstaller"; private static final String CLASS_INSTALL_CERTS = "com.android.certinstaller.CertInstallerMain"; private static final String KEY_INSTALL_CERTIFICATE = "certificate_install_usage"; private static final String INSTALL_CERTIFICATE_VALUE = "wifi"; protected int REQUEST_INSTALL_CERTS = 1; /* Phase2 methods supported by PEAP are limited */ private ArrayAdapter mPhase2PeapAdapter; /* Phase2 methods supported by TTLS are limited */ private ArrayAdapter mPhase2TtlsAdapter; // e.g. WifiEntry.SECURITY_NONE @VisibleForTesting int mWifiEntrySecurity; private TextView mPasswordView; private ImageButton mSsidScanButton; private String mUnspecifiedCertString; private String mMultipleCertSetString; private String mUseSystemCertsString; private String mDoNotProvideEapUserCertString; @VisibleForTesting String mInstallCertsString; private Spinner mSecuritySpinner; @VisibleForTesting Spinner mEapMethodSpinner; private int mLastShownEapMethod; @VisibleForTesting Spinner mEapSimSpinner; // For EAP-SIM, EAP-AKA and EAP-AKA-PRIME. @VisibleForTesting Spinner mEapCaCertSpinner; private Spinner mEapOcspSpinner; private TextView mEapDomainView; private Spinner mPhase2Spinner; // Associated with mPhase2Spinner, one of mPhase2TtlsAdapter or mPhase2PeapAdapter private ArrayAdapter mPhase2Adapter; private Spinner mEapUserCertSpinner; private TextView mEapIdentityView; private TextView mEapAnonymousView; private Spinner mIpSettingsSpinner; private TextView mIpAddressView; private TextView mGatewayView; private TextView mNetworkPrefixLengthView; private TextView mDns1View; private TextView mDns2View; private Spinner mProxySettingsSpinner; private Spinner mMeteredSettingsSpinner; private Spinner mHiddenSettingsSpinner; private Spinner mPrivacySettingsSpinner; private TextView mHiddenWarningView; private TextView mProxyHostView; private TextView mProxyPortView; private TextView mProxyExclusionListView; private TextView mProxyPacView; private CheckBox mSharedCheckBox; private IpAssignment mIpAssignment = IpAssignment.UNASSIGNED; private ProxySettings mProxySettings = ProxySettings.UNASSIGNED; private ProxyInfo mHttpProxy = null; private StaticIpConfiguration mStaticIpConfiguration = null; private String[] mLevels; private int mMode; private TextView mSsidView; private Context mContext; @VisibleForTesting Integer[] mSecurityInPosition; private final WifiManager mWifiManager; private final List mActiveSubscriptionInfos = new ArrayList<>(); public WifiConfigController2(WifiConfigUiBase2 parent, View view, WifiEntry wifiEntry, int mode) { mConfigUi = parent; mView = view; mWifiEntry = wifiEntry; mContext = mConfigUi.getContext(); // Init Wi-Fi manager mWifiManager = (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); initWifiConfigController2(wifiEntry, mode); } @VisibleForTesting public WifiConfigController2(WifiConfigUiBase2 parent, View view, WifiEntry wifiEntry, int mode, WifiManager wifiManager) { mConfigUi = parent; mView = view; mWifiEntry = wifiEntry; mContext = mConfigUi.getContext(); mWifiManager = wifiManager; initWifiConfigController2(wifiEntry, mode); } private void initWifiConfigController2(WifiEntry wifiEntry, int mode) { mWifiEntrySecurity = (wifiEntry == null) ? WifiEntry.SECURITY_NONE : wifiEntry.getSecurity(); mMode = mode; final Resources res = mContext.getResources(); mLevels = res.getStringArray(R.array.wifi_signal); if (Utils.isWifiOnly(mContext) || !mContext.getResources().getBoolean( com.android.internal.R.bool.config_eap_sim_based_auth_supported)) { mPhase2PeapAdapter = getSpinnerAdapter(R.array.wifi_peap_phase2_entries); } else { mPhase2PeapAdapter = getSpinnerAdapterWithEapMethodsTts( R.array.wifi_peap_phase2_entries_with_sim_auth); } mPhase2TtlsAdapter = getSpinnerAdapter(R.array.wifi_ttls_phase2_entries); mUnspecifiedCertString = mContext.getString(R.string.wifi_unspecified); mMultipleCertSetString = mContext.getString(R.string.wifi_multiple_cert_added); mUseSystemCertsString = mContext.getString(R.string.wifi_use_system_certs); mDoNotProvideEapUserCertString = mContext.getString(R.string.wifi_do_not_provide_eap_user_cert); mInstallCertsString = mContext.getString(R.string.wifi_install_credentials); mSsidScanButton = (ImageButton) mView.findViewById(R.id.ssid_scanner_button); mIpSettingsSpinner = (Spinner) mView.findViewById(R.id.ip_settings); mIpSettingsSpinner.setOnItemSelectedListener(this); mProxySettingsSpinner = (Spinner) mView.findViewById(R.id.proxy_settings); mProxySettingsSpinner.setOnItemSelectedListener(this); mSharedCheckBox = (CheckBox) mView.findViewById(R.id.shared); mMeteredSettingsSpinner = mView.findViewById(R.id.metered_settings); mHiddenSettingsSpinner = mView.findViewById(R.id.hidden_settings); mPrivacySettingsSpinner = mView.findViewById(R.id.privacy_settings); if (mWifiManager.isConnectedMacRandomizationSupported()) { View privacySettingsLayout = mView.findViewById(R.id.privacy_settings_fields); privacySettingsLayout.setVisibility(View.VISIBLE); } mHiddenSettingsSpinner.setOnItemSelectedListener(this); mHiddenWarningView = mView.findViewById(R.id.hidden_settings_warning); mHiddenWarningView.setVisibility( mHiddenSettingsSpinner.getSelectedItemPosition() == NOT_HIDDEN_NETWORK ? View.GONE : View.VISIBLE); mSecurityInPosition = new Integer[WifiEntry.NUM_SECURITY_TYPES]; if (mWifiEntry == null) { // new network configureSecuritySpinner(); mConfigUi.setSubmitButton(res.getString(R.string.wifi_save)); } else { mConfigUi.setTitle(mWifiEntry.getTitle()); ViewGroup group = (ViewGroup) mView.findViewById(R.id.info); boolean showAdvancedFields = false; if (mWifiEntry.isSaved()) { WifiConfiguration config = mWifiEntry.getWifiConfiguration(); mMeteredSettingsSpinner.setSelection(config.meteredOverride); mHiddenSettingsSpinner.setSelection(config.hiddenSSID ? HIDDEN_NETWORK : NOT_HIDDEN_NETWORK); final int prefMacValue = WifiPrivacyPreferenceController2 .translateMacRandomizedValueToPrefValue(config.macRandomizationSetting); mPrivacySettingsSpinner.setSelection(prefMacValue); if (config.getIpConfiguration().getIpAssignment() == IpAssignment.STATIC) { mIpSettingsSpinner.setSelection(STATIC_IP); showAdvancedFields = true; // Display IP address. StaticIpConfiguration staticConfig = config.getIpConfiguration() .getStaticIpConfiguration(); if (staticConfig != null && staticConfig.getIpAddress() != null) { addRow(group, R.string.wifi_ip_address, staticConfig.getIpAddress().getAddress().getHostAddress()); } } else { mIpSettingsSpinner.setSelection(DHCP); } mSharedCheckBox.setEnabled(config.shared); if (!config.shared) { showAdvancedFields = true; } ProxySettings proxySettings = config.getIpConfiguration().getProxySettings(); if (proxySettings == ProxySettings.STATIC) { mProxySettingsSpinner.setSelection(PROXY_STATIC); showAdvancedFields = true; } else if (proxySettings == ProxySettings.PAC) { mProxySettingsSpinner.setSelection(PROXY_PAC); showAdvancedFields = true; } else { mProxySettingsSpinner.setSelection(PROXY_NONE); } if (config != null && config.isPasspoint()) { addRow(group, R.string.passpoint_label, String.format(mContext.getString(R.string.passpoint_content), config.providerFriendlyName)); } } if ((!mWifiEntry.isSaved() && mWifiEntry.getConnectedState() != WifiEntry.CONNECTED_STATE_CONNECTED && !mWifiEntry.isSubscription()) || mMode != WifiConfigUiBase2.MODE_VIEW) { showSecurityFields(/* refreshEapMethods */ true, /* refreshCertificates */ true); showIpConfigFields(); showProxyFields(); final CheckBox advancedTogglebox = (CheckBox) mView.findViewById(R.id.wifi_advanced_togglebox); if (!showAdvancedFields) { // Need to show Advanced Option button. mView.findViewById(R.id.wifi_advanced_toggle).setVisibility(View.VISIBLE); advancedTogglebox.setOnCheckedChangeListener(this); advancedTogglebox.setChecked(showAdvancedFields); setAdvancedOptionAccessibilityString(); } mView.findViewById(R.id.wifi_advanced_fields) .setVisibility(showAdvancedFields ? View.VISIBLE : View.GONE); } if (mMode == WifiConfigUiBase2.MODE_MODIFY) { mConfigUi.setSubmitButton(res.getString(R.string.wifi_save)); } else if (mMode == WifiConfigUiBase2.MODE_CONNECT) { mConfigUi.setSubmitButton(res.getString(R.string.wifi_connect)); } else { final String signalLevel = getSignalString(); if (mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_DISCONNECTED && signalLevel != null) { mConfigUi.setSubmitButton(res.getString(R.string.wifi_connect)); } else { if (signalLevel != null) { addRow(group, R.string.wifi_signal, signalLevel); } final ConnectedInfo info = mWifiEntry.getConnectedInfo(); if (info != null && info.linkSpeedMbps >= 0) { addRow(group, R.string.wifi_speed, String.format( res.getString(R.string.link_speed), info.linkSpeedMbps)); } if (info != null && info.frequencyMhz != WifiEntry.FREQUENCY_UNKNOWN) { final int frequency = info.frequencyMhz; String band = null; if (frequency >= WifiEntry.MIN_FREQ_24GHZ && frequency < WifiEntry.MAX_FREQ_24GHZ) { band = res.getString(R.string.wifi_band_24ghz); } else if (frequency >= WifiEntry.MIN_FREQ_5GHZ && frequency < WifiEntry.MAX_FREQ_5GHZ) { band = res.getString(R.string.wifi_band_5ghz); } else if (frequency >= WifiEntry.MIN_FREQ_6GHZ && frequency < WifiEntry.MAX_FREQ_6GHZ) { band = res.getString(R.string.wifi_band_6ghz); } else { Log.e(TAG, "Unexpected frequency " + frequency); } if (band != null) { addRow(group, R.string.wifi_frequency, band); } } addRow(group, R.string.wifi_security, mWifiEntry.getSecurityString(false /* concise */)); mView.findViewById(R.id.ip_fields).setVisibility(View.GONE); } if (mWifiEntry.isSaved() || mWifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED || mWifiEntry.isSubscription()) { mConfigUi.setForgetButton(res.getString(R.string.wifi_forget)); } } mSsidScanButton.setVisibility(View.GONE); } mSharedCheckBox.setVisibility(View.GONE); mConfigUi.setCancelButton(res.getString(R.string.wifi_cancel)); if (mConfigUi.getSubmitButton() != null) { enableSubmitIfAppropriate(); } // After done view show and hide, request focus mView.findViewById(R.id.l_wifidialog).requestFocus(); } private void addRow(ViewGroup group, int name, String value) { View row = mConfigUi.getLayoutInflater().inflate(R.layout.wifi_dialog_row, group, false); ((TextView) row.findViewById(R.id.name)).setText(name); ((TextView) row.findViewById(R.id.value)).setText(value); group.addView(row); } @VisibleForTesting String getSignalString() { if (mWifiEntry.getLevel() == WifiEntry.WIFI_LEVEL_UNREACHABLE) { return null; } final int level = mWifiEntry.getLevel(); return (level > -1 && level < mLevels.length) ? mLevels[level] : null; } void hideForgetButton() { Button forget = mConfigUi.getForgetButton(); if (forget == null) return; forget.setVisibility(View.GONE); } void hideSubmitButton() { Button submit = mConfigUi.getSubmitButton(); if (submit == null) return; submit.setVisibility(View.GONE); } /* show submit button if password, ip and proxy settings are valid */ void enableSubmitIfAppropriate() { Button submit = mConfigUi.getSubmitButton(); if (submit == null) return; submit.setEnabled(isSubmittable()); } boolean isValidPsk(String password) { if (password.length() == 64 && password.matches("[0-9A-Fa-f]{64}")) { return true; } else if (password.length() >= 8 && password.length() <= 63) { return true; } return false; } boolean isValidSaePassword(String password) { if (password.length() >= 1 && password.length() <= 128) { return true; } return false; } boolean isSubmittable() { boolean enabled = false; boolean passwordInvalid = false; if (mPasswordView != null && ((mWifiEntrySecurity == WifiEntry.SECURITY_WEP && mPasswordView.length() == 0) || (mWifiEntrySecurity == WifiEntry.SECURITY_PSK && !isValidPsk(mPasswordView.getText().toString())) || (mWifiEntrySecurity == WifiEntry.SECURITY_SAE && !isValidSaePassword(mPasswordView.getText().toString())))) { passwordInvalid = true; } if ((mSsidView != null && mSsidView.length() == 0) // If WifiEntry is not saved, apply passwordInvalid check || ((mWifiEntry == null || !mWifiEntry.isSaved()) && passwordInvalid // If WifiEntry is saved (modifying network) and password is changed, apply // Invalid password check || mWifiEntry != null && mWifiEntry.isSaved() && passwordInvalid && mPasswordView.length() > 0)) { enabled = false; } else { enabled = ipAndProxyFieldsAreValid(); } if ((mWifiEntrySecurity == WifiEntry.SECURITY_EAP || mWifiEntrySecurity == WifiEntry.SECURITY_EAP_WPA3_ENTERPRISE || mWifiEntrySecurity == WifiEntry.SECURITY_EAP_SUITE_B) && mEapCaCertSpinner != null && mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE) { String caCertSelection = (String) mEapCaCertSpinner.getSelectedItem(); if (caCertSelection.equals(mUnspecifiedCertString)) { // Disallow submit if the user has not selected a CA certificate for an EAP network // configuration. enabled = false; } else if (mEapDomainView != null && mView.findViewById(R.id.l_domain).getVisibility() != View.GONE && TextUtils.isEmpty(mEapDomainView.getText().toString())) { // Disallow submit if the user chooses to use a certificate for EAP server // validation, but does not provide a domain. enabled = false; } } if ((mWifiEntrySecurity == WifiEntry.SECURITY_EAP || mWifiEntrySecurity == WifiEntry.SECURITY_EAP_WPA3_ENTERPRISE || mWifiEntrySecurity == WifiEntry.SECURITY_EAP_SUITE_B) && mEapUserCertSpinner != null && mView.findViewById(R.id.l_user_cert).getVisibility() != View.GONE && mEapUserCertSpinner.getSelectedItem().equals(mUnspecifiedCertString)) { // Disallow submit if the user has not selected a user certificate for an EAP network // configuration. enabled = false; } return enabled; } void showWarningMessagesIfAppropriate() { mView.findViewById(R.id.no_user_cert_warning).setVisibility(View.GONE); mView.findViewById(R.id.no_domain_warning).setVisibility(View.GONE); mView.findViewById(R.id.ssid_too_long_warning).setVisibility(View.GONE); if (mSsidView != null) { final String ssid = mSsidView.getText().toString(); if (WifiUtils.isSSIDTooLong(ssid)) { mView.findViewById(R.id.ssid_too_long_warning).setVisibility(View.VISIBLE); } } if (mEapCaCertSpinner != null && mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE) { if (mEapDomainView != null && mView.findViewById(R.id.l_domain).getVisibility() != View.GONE && TextUtils.isEmpty(mEapDomainView.getText().toString())) { // Display warning if user chooses to use a certificate without restricting the // server domain that these certificates can be used to validate. mView.findViewById(R.id.no_domain_warning).setVisibility(View.VISIBLE); } } if (mWifiEntrySecurity == WifiEntry.SECURITY_EAP_SUITE_B && mEapMethodSpinner.getSelectedItemPosition() == WIFI_EAP_METHOD_TLS) { String userCertSelection = (String) mEapUserCertSpinner.getSelectedItem(); if (userCertSelection.equals(mUnspecifiedCertString)) { mView.findViewById(R.id.no_user_cert_warning).setVisibility(View.VISIBLE); } } } /** * @return {@link WifiConfiguration} from mWifiEntry and UI edit result. */ public WifiConfiguration getConfig() { if (mMode == WifiConfigUiBase2.MODE_VIEW) { return null; } WifiConfiguration config = new WifiConfiguration(); if (mWifiEntry == null) { config.SSID = "\"" + mSsidView.getText().toString() + "\""; // If the user adds a network manually, assume that it is hidden. config.hiddenSSID = mHiddenSettingsSpinner.getSelectedItemPosition() == HIDDEN_NETWORK; } else if (!mWifiEntry.isSaved()) { config.SSID = "\"" + mWifiEntry.getTitle() + "\""; } else { config.networkId = mWifiEntry.getWifiConfiguration().networkId; config.hiddenSSID = mWifiEntry.getWifiConfiguration().hiddenSSID; } config.shared = mSharedCheckBox.isChecked(); switch (mWifiEntrySecurity) { case WifiEntry.SECURITY_NONE: config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OPEN); break; case WifiEntry.SECURITY_WEP: config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_WEP); if (mPasswordView.length() != 0) { int length = mPasswordView.length(); String password = mPasswordView.getText().toString(); // WEP-40, WEP-104, and 256-bit WEP (WEP-232?) if ((length == 10 || length == 26 || length == 58) && password.matches("[0-9A-Fa-f]*")) { config.wepKeys[0] = password; } else { config.wepKeys[0] = '"' + password + '"'; } } break; case WifiEntry.SECURITY_PSK: config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_PSK); if (mPasswordView.length() != 0) { String password = mPasswordView.getText().toString(); if (password.matches("[0-9A-Fa-f]{64}")) { config.preSharedKey = password; } else { config.preSharedKey = '"' + password + '"'; } } break; case WifiEntry.SECURITY_EAP: case WifiEntry.SECURITY_EAP_WPA3_ENTERPRISE: case WifiEntry.SECURITY_EAP_SUITE_B: if (mWifiEntrySecurity == WifiEntry.SECURITY_EAP_SUITE_B) { // allowedSuiteBCiphers will be set according to certificate type config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_SUITE_B); } else if (mWifiEntrySecurity == WifiEntry.SECURITY_EAP_WPA3_ENTERPRISE) { config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP_WPA3_ENTERPRISE); } else { config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_EAP); } config.enterpriseConfig = new WifiEnterpriseConfig(); int eapMethod = mEapMethodSpinner.getSelectedItemPosition(); int phase2Method = mPhase2Spinner.getSelectedItemPosition(); config.enterpriseConfig.setEapMethod(eapMethod); switch (eapMethod) { case Eap.PEAP: // PEAP supports limited phase2 values // Map the index from the mPhase2PeapAdapter to the one used // by the API which has the full list of PEAP methods. switch(phase2Method) { case WIFI_PEAP_PHASE2_MSCHAPV2: config.enterpriseConfig.setPhase2Method(Phase2.MSCHAPV2); break; case WIFI_PEAP_PHASE2_GTC: config.enterpriseConfig.setPhase2Method(Phase2.GTC); break; case WIFI_PEAP_PHASE2_SIM: config.enterpriseConfig.setPhase2Method(Phase2.SIM); break; case WIFI_PEAP_PHASE2_AKA: config.enterpriseConfig.setPhase2Method(Phase2.AKA); break; case WIFI_PEAP_PHASE2_AKA_PRIME: config.enterpriseConfig.setPhase2Method(Phase2.AKA_PRIME); break; default: Log.e(TAG, "Unknown phase2 method" + phase2Method); break; } break; case Eap.TTLS: // The default index from mPhase2TtlsAdapter maps to the API switch(phase2Method) { case WIFI_TTLS_PHASE2_PAP: config.enterpriseConfig.setPhase2Method(Phase2.PAP); break; case WIFI_TTLS_PHASE2_MSCHAP: config.enterpriseConfig.setPhase2Method(Phase2.MSCHAP); break; case WIFI_TTLS_PHASE2_MSCHAPV2: config.enterpriseConfig.setPhase2Method(Phase2.MSCHAPV2); break; case WIFI_TTLS_PHASE2_GTC: config.enterpriseConfig.setPhase2Method(Phase2.GTC); break; default: Log.e(TAG, "Unknown phase2 method" + phase2Method); break; } break; default: break; } if (config.enterpriseConfig.isAuthenticationSimBased() && mActiveSubscriptionInfos.size() > 0) { config.carrierId = mActiveSubscriptionInfos .get(mEapSimSpinner.getSelectedItemPosition()).getCarrierId(); } String caCert = (String) mEapCaCertSpinner.getSelectedItem(); config.enterpriseConfig.setCaCertificateAliases(null); config.enterpriseConfig.setCaPath(null); config.enterpriseConfig.setDomainSuffixMatch(mEapDomainView.getText().toString()); if (caCert.equals(mUnspecifiedCertString)) { // ca_cert already set to null, so do nothing. } else if (caCert.equals(mUseSystemCertsString)) { config.enterpriseConfig.setCaPath(SYSTEM_CA_STORE_PATH); } else if (caCert.equals(mMultipleCertSetString)) { if (mWifiEntry != null) { if (!mWifiEntry.isSaved()) { Log.e(TAG, "Multiple certs can only be set " + "when editing saved network"); } config.enterpriseConfig.setCaCertificateAliases( mWifiEntry.getWifiConfiguration() .enterpriseConfig .getCaCertificateAliases()); } } else { config.enterpriseConfig.setCaCertificateAliases(new String[] {caCert}); } // ca_cert or ca_path should not both be non-null, since we only intend to let // the use either their own certificate, or the system certificates, not both. // The variable that is not used must explicitly be set to null, so that a // previously-set value on a saved configuration will be erased on an update. if (config.enterpriseConfig.getCaCertificateAliases() != null && config.enterpriseConfig.getCaPath() != null) { Log.e(TAG, "ca_cert (" + config.enterpriseConfig.getCaCertificateAliases() + ") and ca_path (" + config.enterpriseConfig.getCaPath() + ") should not both be non-null"); } // Only set OCSP option if there is a valid CA certificate. if (caCert.equals(mUnspecifiedCertString)) { config.enterpriseConfig.setOcsp(WifiEnterpriseConfig.OCSP_NONE); } else { config.enterpriseConfig.setOcsp(mEapOcspSpinner.getSelectedItemPosition()); } String clientCert = (String) mEapUserCertSpinner.getSelectedItem(); if (clientCert.equals(mUnspecifiedCertString) || clientCert.equals(mDoNotProvideEapUserCertString)) { // Note: |clientCert| should not be able to take the value |unspecifiedCert|, // since we prevent such configurations from being saved. clientCert = ""; } config.enterpriseConfig.setClientCertificateAlias(clientCert); if (eapMethod == Eap.SIM || eapMethod == Eap.AKA || eapMethod == Eap.AKA_PRIME) { config.enterpriseConfig.setIdentity(""); config.enterpriseConfig.setAnonymousIdentity(""); } else if (eapMethod == Eap.PWD) { config.enterpriseConfig.setIdentity(mEapIdentityView.getText().toString()); config.enterpriseConfig.setAnonymousIdentity(""); } else { config.enterpriseConfig.setIdentity(mEapIdentityView.getText().toString()); config.enterpriseConfig.setAnonymousIdentity( mEapAnonymousView.getText().toString()); } if (mPasswordView.isShown()) { // For security reasons, a previous password is not displayed to user. // Update only if it has been changed. if (mPasswordView.length() > 0) { config.enterpriseConfig.setPassword(mPasswordView.getText().toString()); } } else { // clear password config.enterpriseConfig.setPassword(mPasswordView.getText().toString()); } break; case WifiEntry.SECURITY_SAE: config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_SAE); if (mPasswordView.length() != 0) { String password = mPasswordView.getText().toString(); config.preSharedKey = '"' + password + '"'; } break; case WifiEntry.SECURITY_OWE: config.setSecurityParams(WifiConfiguration.SECURITY_TYPE_OWE); break; default: return null; } final IpConfiguration ipConfig = new IpConfiguration(); ipConfig.setIpAssignment(mIpAssignment); ipConfig.setProxySettings(mProxySettings); ipConfig.setStaticIpConfiguration(mStaticIpConfiguration); ipConfig.setHttpProxy(mHttpProxy); config.setIpConfiguration(ipConfig); if (mMeteredSettingsSpinner != null) { config.meteredOverride = mMeteredSettingsSpinner.getSelectedItemPosition(); } if (mPrivacySettingsSpinner != null) { config.macRandomizationSetting = WifiPrivacyPreferenceController2 .translatePrefValueToMacRandomizedValue(mPrivacySettingsSpinner .getSelectedItemPosition()); } return config; } private boolean ipAndProxyFieldsAreValid() { mIpAssignment = (mIpSettingsSpinner != null && mIpSettingsSpinner.getSelectedItemPosition() == STATIC_IP) ? IpAssignment.STATIC : IpAssignment.DHCP; if (mIpAssignment == IpAssignment.STATIC) { mStaticIpConfiguration = new StaticIpConfiguration(); int result = validateIpConfigFields(mStaticIpConfiguration); if (result != 0) { return false; } } final int selectedPosition = mProxySettingsSpinner.getSelectedItemPosition(); mProxySettings = ProxySettings.NONE; mHttpProxy = null; if (selectedPosition == PROXY_STATIC && mProxyHostView != null) { mProxySettings = ProxySettings.STATIC; String host = mProxyHostView.getText().toString(); String portStr = mProxyPortView.getText().toString(); String exclusionList = mProxyExclusionListView.getText().toString(); int port = 0; int result = 0; try { port = Integer.parseInt(portStr); result = ProxySelector.validate(host, portStr, exclusionList); } catch (NumberFormatException e) { result = R.string.proxy_error_invalid_port; } if (result == 0) { mHttpProxy = ProxyInfo.buildDirectProxy( host, port, Arrays.asList(exclusionList.split(","))); } else { return false; } } else if (selectedPosition == PROXY_PAC && mProxyPacView != null) { mProxySettings = ProxySettings.PAC; CharSequence uriSequence = mProxyPacView.getText(); if (TextUtils.isEmpty(uriSequence)) { return false; } Uri uri = Uri.parse(uriSequence.toString()); if (uri == null) { return false; } mHttpProxy = ProxyInfo.buildPacProxy(uri); } return true; } private Inet4Address getIPv4Address(String text) { try { return (Inet4Address) InetAddresses.parseNumericAddress(text); } catch (IllegalArgumentException | ClassCastException e) { return null; } } private int validateIpConfigFields(StaticIpConfiguration staticIpConfiguration) { if (mIpAddressView == null) return 0; String ipAddr = mIpAddressView.getText().toString(); if (TextUtils.isEmpty(ipAddr)) return R.string.wifi_ip_settings_invalid_ip_address; Inet4Address inetAddr = getIPv4Address(ipAddr); if (inetAddr == null || inetAddr.equals(Inet4Address.ANY)) { return R.string.wifi_ip_settings_invalid_ip_address; } // Copy all fields into the builder first and set desired value later with builder. final StaticIpConfiguration.Builder staticIPBuilder = new StaticIpConfiguration.Builder() .setDnsServers(staticIpConfiguration.getDnsServers()) .setDomains(staticIpConfiguration.getDomains()) .setGateway(staticIpConfiguration.getGateway()) .setIpAddress(staticIpConfiguration.getIpAddress()); try { int networkPrefixLength = -1; try { networkPrefixLength = Integer.parseInt(mNetworkPrefixLengthView.getText().toString()); if (networkPrefixLength < 0 || networkPrefixLength > 32) { return R.string.wifi_ip_settings_invalid_network_prefix_length; } staticIPBuilder.setIpAddress(new LinkAddress(inetAddr, networkPrefixLength)); } catch (NumberFormatException e) { // Set the hint as default after user types in ip address mNetworkPrefixLengthView.setText(mConfigUi.getContext().getString( R.string.wifi_network_prefix_length_hint)); } catch (IllegalArgumentException e) { return R.string.wifi_ip_settings_invalid_ip_address; } String gateway = mGatewayView.getText().toString(); if (TextUtils.isEmpty(gateway)) { try { //Extract a default gateway from IP address InetAddress netPart = NetUtils.getNetworkPart(inetAddr, networkPrefixLength); byte[] addr = netPart.getAddress(); addr[addr.length - 1] = 1; mGatewayView.setText(InetAddress.getByAddress(addr).getHostAddress()); } catch (RuntimeException ee) { } catch (java.net.UnknownHostException u) { } } else { InetAddress gatewayAddr = getIPv4Address(gateway); if (gatewayAddr == null) { return R.string.wifi_ip_settings_invalid_gateway; } if (gatewayAddr.isMulticastAddress()) { return R.string.wifi_ip_settings_invalid_gateway; } staticIPBuilder.setGateway(gatewayAddr); } String dns = mDns1View.getText().toString(); InetAddress dnsAddr = null; final ArrayList dnsServers = new ArrayList<>(); if (TextUtils.isEmpty(dns)) { //If everything else is valid, provide hint as a default option mDns1View.setText(mConfigUi.getContext().getString(R.string.wifi_dns1_hint)); } else { dnsAddr = getIPv4Address(dns); if (dnsAddr == null) { return R.string.wifi_ip_settings_invalid_dns; } dnsServers.add(dnsAddr); } if (mDns2View.length() > 0) { dns = mDns2View.getText().toString(); dnsAddr = getIPv4Address(dns); if (dnsAddr == null) { return R.string.wifi_ip_settings_invalid_dns; } dnsServers.add(dnsAddr); } staticIPBuilder.setDnsServers(dnsServers); return 0; } finally { // Caller of this method may rely on staticIpConfiguration, so build the final result // at the end of the method. mStaticIpConfiguration = staticIPBuilder.build(); } } protected void showSecurityFields(boolean refreshEapMethods, boolean refreshCertificates) { if (mWifiEntrySecurity == WifiEntry.SECURITY_NONE || mWifiEntrySecurity == WifiEntry.SECURITY_OWE) { mView.findViewById(R.id.security_fields).setVisibility(View.GONE); return; } mView.findViewById(R.id.security_fields).setVisibility(View.VISIBLE); if (mPasswordView == null) { mPasswordView = (TextView) mView.findViewById(R.id.password); mPasswordView.addTextChangedListener(this); mPasswordView.setOnEditorActionListener(this); mPasswordView.setOnKeyListener(this); ((CheckBox) mView.findViewById(R.id.show_password)) .setOnCheckedChangeListener(this); if (mWifiEntry != null && mWifiEntry.isSaved()) { mPasswordView.setHint(R.string.wifi_unchanged); } } if (mWifiEntrySecurity != WifiEntry.SECURITY_EAP && mWifiEntrySecurity != WifiEntry.SECURITY_EAP_WPA3_ENTERPRISE && mWifiEntrySecurity != WifiEntry.SECURITY_EAP_SUITE_B) { mView.findViewById(R.id.eap).setVisibility(View.GONE); return; } mView.findViewById(R.id.eap).setVisibility(View.VISIBLE); // TODO (b/140541213): Maybe we can remove initiateEnterpriseNetworkUi by moving code block boolean initiateEnterpriseNetworkUi = false; if (mEapMethodSpinner == null) { initiateEnterpriseNetworkUi = true; mEapMethodSpinner = (Spinner) mView.findViewById(R.id.method); mEapMethodSpinner.setOnItemSelectedListener(this); mEapSimSpinner = (Spinner) mView.findViewById(R.id.sim); mPhase2Spinner = (Spinner) mView.findViewById(R.id.phase2); mPhase2Spinner.setOnItemSelectedListener(this); mEapCaCertSpinner = (Spinner) mView.findViewById(R.id.ca_cert); mEapCaCertSpinner.setOnItemSelectedListener(this); mEapOcspSpinner = (Spinner) mView.findViewById(R.id.ocsp); mEapDomainView = (TextView) mView.findViewById(R.id.domain); mEapDomainView.addTextChangedListener(this); mEapUserCertSpinner = (Spinner) mView.findViewById(R.id.user_cert); mEapUserCertSpinner.setOnItemSelectedListener(this); mEapIdentityView = (TextView) mView.findViewById(R.id.identity); mEapAnonymousView = (TextView) mView.findViewById(R.id.anonymous); setAccessibilityDelegateForSecuritySpinners(); } if (refreshEapMethods) { ArrayAdapter eapMethodSpinnerAdapter; if (mWifiEntrySecurity == WifiEntry.SECURITY_EAP_SUITE_B) { eapMethodSpinnerAdapter = getSpinnerAdapter(R.array.wifi_eap_method); mEapMethodSpinner.setAdapter(eapMethodSpinnerAdapter); // WAP3-Enterprise 192-bit only allows EAP method TLS mEapMethodSpinner.setSelection(Eap.TLS); mEapMethodSpinner.setEnabled(false); } else if (Utils.isWifiOnly(mContext) || !mContext.getResources().getBoolean( com.android.internal.R.bool.config_eap_sim_based_auth_supported)) { eapMethodSpinnerAdapter = getSpinnerAdapter(R.array.eap_method_without_sim_auth); mEapMethodSpinner.setAdapter(eapMethodSpinnerAdapter); mEapMethodSpinner.setEnabled(true); } else { eapMethodSpinnerAdapter = getSpinnerAdapterWithEapMethodsTts( R.array.wifi_eap_method); mEapMethodSpinner.setAdapter(eapMethodSpinnerAdapter); mEapMethodSpinner.setEnabled(true); } } if (refreshCertificates) { loadSims(); final AndroidKeystoreAliasLoader androidKeystoreAliasLoader = getAndroidKeystoreAliasLoader(); loadCertificates( mEapCaCertSpinner, androidKeystoreAliasLoader.getCaCertAliases(), null /* noCertificateString */, false /* showMultipleCerts */, true /* showUsePreinstalledCertOption */); loadCertificates( mEapUserCertSpinner, androidKeystoreAliasLoader.getKeyCertAliases(), mDoNotProvideEapUserCertString, false /* showMultipleCerts */, false /* showUsePreinstalledCertOption */); setSelection(mEapCaCertSpinner, mUnspecifiedCertString); } // Modifying an existing network if (initiateEnterpriseNetworkUi && mWifiEntry != null && mWifiEntry.isSaved()) { final WifiConfiguration wifiConfig = mWifiEntry.getWifiConfiguration(); final WifiEnterpriseConfig enterpriseConfig = wifiConfig.enterpriseConfig; final int eapMethod = enterpriseConfig.getEapMethod(); final int phase2Method = enterpriseConfig.getPhase2Method(); mEapMethodSpinner.setSelection(eapMethod); mLastShownEapMethod = eapMethod; showEapFieldsByMethod(eapMethod); switch (eapMethod) { case Eap.PEAP: switch (phase2Method) { case Phase2.MSCHAPV2: mPhase2Spinner.setSelection(WIFI_PEAP_PHASE2_MSCHAPV2); break; case Phase2.GTC: mPhase2Spinner.setSelection(WIFI_PEAP_PHASE2_GTC); break; case Phase2.SIM: mPhase2Spinner.setSelection(WIFI_PEAP_PHASE2_SIM); break; case Phase2.AKA: mPhase2Spinner.setSelection(WIFI_PEAP_PHASE2_AKA); break; case Phase2.AKA_PRIME: mPhase2Spinner.setSelection(WIFI_PEAP_PHASE2_AKA_PRIME); break; default: Log.e(TAG, "Invalid phase 2 method " + phase2Method); break; } break; case Eap.TTLS: switch (phase2Method) { case Phase2.PAP: mPhase2Spinner.setSelection(WIFI_TTLS_PHASE2_PAP); break; case Phase2.MSCHAP: mPhase2Spinner.setSelection(WIFI_TTLS_PHASE2_MSCHAP); break; case Phase2.MSCHAPV2: mPhase2Spinner.setSelection(WIFI_TTLS_PHASE2_MSCHAPV2); break; case Phase2.GTC: mPhase2Spinner.setSelection(WIFI_TTLS_PHASE2_GTC); break; default: Log.e(TAG, "Invalid phase 2 method " + phase2Method); break; } break; default: break; } if (enterpriseConfig.isAuthenticationSimBased()) { for (int i = 0; i < mActiveSubscriptionInfos.size(); i++) { if (wifiConfig.carrierId == mActiveSubscriptionInfos.get(i).getCarrierId()) { mEapSimSpinner.setSelection(i); break; } } } if (!TextUtils.isEmpty(enterpriseConfig.getCaPath())) { setSelection(mEapCaCertSpinner, mUseSystemCertsString); } else { String[] caCerts = enterpriseConfig.getCaCertificateAliases(); if (caCerts == null) { setSelection(mEapCaCertSpinner, mUnspecifiedCertString); } else if (caCerts.length == 1) { setSelection(mEapCaCertSpinner, caCerts[0]); } else { // Reload the cert spinner with an extra "multiple certificates added" item. final AndroidKeystoreAliasLoader androidKeystoreAliasLoader = getAndroidKeystoreAliasLoader(); loadCertificates( mEapCaCertSpinner, androidKeystoreAliasLoader.getCaCertAliases(), null /* noCertificateString */, true /* showMultipleCerts */, true /* showUsePreinstalledCertOption */); setSelection(mEapCaCertSpinner, mMultipleCertSetString); } } mEapOcspSpinner.setSelection(enterpriseConfig.getOcsp()); mEapDomainView.setText(enterpriseConfig.getDomainSuffixMatch()); String userCert = enterpriseConfig.getClientCertificateAlias(); if (TextUtils.isEmpty(userCert)) { setSelection(mEapUserCertSpinner, mDoNotProvideEapUserCertString); } else { setSelection(mEapUserCertSpinner, userCert); } mEapIdentityView.setText(enterpriseConfig.getIdentity()); mEapAnonymousView.setText(enterpriseConfig.getAnonymousIdentity()); } else { showEapFieldsByMethod(mEapMethodSpinner.getSelectedItemPosition()); } } private void setAccessibilityDelegateForSecuritySpinners() { final AccessibilityDelegate selectedEventBlocker = new AccessibilityDelegate() { @Override public void sendAccessibilityEvent(View host, int eventType) { if (eventType == AccessibilityEvent.TYPE_VIEW_SELECTED) { // Ignore TYPE_VIEW_SELECTED or there will be multiple Spinner selected // information for WifiController2#showSecurityFields. return; } super.sendAccessibilityEvent(host, eventType); } }; mEapMethodSpinner.setAccessibilityDelegate(selectedEventBlocker); mPhase2Spinner.setAccessibilityDelegate(selectedEventBlocker); mEapCaCertSpinner.setAccessibilityDelegate(selectedEventBlocker); mEapOcspSpinner.setAccessibilityDelegate(selectedEventBlocker); mEapUserCertSpinner.setAccessibilityDelegate(selectedEventBlocker); } /** * EAP-PWD valid fields include * identity * password * EAP-PEAP valid fields include * phase2: MSCHAPV2, GTC, SIM, AKA, AKA' * ca_cert * identity * anonymous_identity * password (not required for SIM, AKA, AKA') * EAP-TLS valid fields include * user_cert * ca_cert * domain * identity * EAP-TTLS valid fields include * phase2: PAP, MSCHAP, MSCHAPV2, GTC * ca_cert * identity * anonymous_identity * password */ private void showEapFieldsByMethod(int eapMethod) { // Common defaults mView.findViewById(R.id.l_method).setVisibility(View.VISIBLE); mView.findViewById(R.id.l_identity).setVisibility(View.VISIBLE); mView.findViewById(R.id.l_domain).setVisibility(View.VISIBLE); // Defaults for most of the EAP methods and over-riden by // by certain EAP methods mView.findViewById(R.id.l_ca_cert).setVisibility(View.VISIBLE); mView.findViewById(R.id.l_ocsp).setVisibility(View.VISIBLE); mView.findViewById(R.id.password_layout).setVisibility(View.VISIBLE); mView.findViewById(R.id.show_password_layout).setVisibility(View.VISIBLE); mView.findViewById(R.id.l_sim).setVisibility(View.VISIBLE); Context context = mConfigUi.getContext(); switch (eapMethod) { case WIFI_EAP_METHOD_PWD: setPhase2Invisible(); setCaCertInvisible(); setOcspInvisible(); setDomainInvisible(); setAnonymousIdentInvisible(); setUserCertInvisible(); mView.findViewById(R.id.l_sim).setVisibility(View.GONE); break; case WIFI_EAP_METHOD_TLS: mView.findViewById(R.id.l_user_cert).setVisibility(View.VISIBLE); setPhase2Invisible(); setAnonymousIdentInvisible(); setPasswordInvisible(); mView.findViewById(R.id.l_sim).setVisibility(View.GONE); break; case WIFI_EAP_METHOD_PEAP: // Reset adapter if needed if (mPhase2Adapter != mPhase2PeapAdapter) { mPhase2Adapter = mPhase2PeapAdapter; mPhase2Spinner.setAdapter(mPhase2Adapter); } mView.findViewById(R.id.l_phase2).setVisibility(View.VISIBLE); mView.findViewById(R.id.l_anonymous).setVisibility(View.VISIBLE); showPeapFields(); setUserCertInvisible(); break; case WIFI_EAP_METHOD_TTLS: // Reset adapter if needed if (mPhase2Adapter != mPhase2TtlsAdapter) { mPhase2Adapter = mPhase2TtlsAdapter; mPhase2Spinner.setAdapter(mPhase2Adapter); } mView.findViewById(R.id.l_phase2).setVisibility(View.VISIBLE); mView.findViewById(R.id.l_anonymous).setVisibility(View.VISIBLE); setUserCertInvisible(); mView.findViewById(R.id.l_sim).setVisibility(View.GONE); break; case WIFI_EAP_METHOD_SIM: case WIFI_EAP_METHOD_AKA: case WIFI_EAP_METHOD_AKA_PRIME: setPhase2Invisible(); setAnonymousIdentInvisible(); setCaCertInvisible(); setOcspInvisible(); setDomainInvisible(); setUserCertInvisible(); setPasswordInvisible(); setIdentityInvisible(); break; } if (mView.findViewById(R.id.l_ca_cert).getVisibility() != View.GONE) { String eapCertSelection = (String) mEapCaCertSpinner.getSelectedItem(); if (eapCertSelection.equals(mUnspecifiedCertString)) { // Domain suffix matching is not relevant if the user hasn't chosen a CA // certificate yet, or chooses not to validate the EAP server. setDomainInvisible(); // Ocsp is an additional validation step for a server certifidate. // This field is not relevant if the user hasn't chosen a valid // CA certificate yet. setOcspInvisible(); } } } private void showPeapFields() { int phase2Method = mPhase2Spinner.getSelectedItemPosition(); if (phase2Method == WIFI_PEAP_PHASE2_SIM || phase2Method == WIFI_PEAP_PHASE2_AKA || phase2Method == WIFI_PEAP_PHASE2_AKA_PRIME) { mEapIdentityView.setText(""); mView.findViewById(R.id.l_identity).setVisibility(View.GONE); setPasswordInvisible(); mView.findViewById(R.id.l_sim).setVisibility(View.VISIBLE); } else { mView.findViewById(R.id.l_identity).setVisibility(View.VISIBLE); mView.findViewById(R.id.l_anonymous).setVisibility(View.VISIBLE); mView.findViewById(R.id.password_layout).setVisibility(View.VISIBLE); mView.findViewById(R.id.show_password_layout).setVisibility(View.VISIBLE); mView.findViewById(R.id.l_sim).setVisibility(View.GONE); } } private void setIdentityInvisible() { mView.findViewById(R.id.l_identity).setVisibility(View.GONE); } private void setPhase2Invisible() { mView.findViewById(R.id.l_phase2).setVisibility(View.GONE); } private void setCaCertInvisible() { mView.findViewById(R.id.l_ca_cert).setVisibility(View.GONE); setSelection(mEapCaCertSpinner, mUnspecifiedCertString); } private void setOcspInvisible() { mView.findViewById(R.id.l_ocsp).setVisibility(View.GONE); mEapOcspSpinner.setSelection(WifiEnterpriseConfig.OCSP_NONE); } private void setDomainInvisible() { mView.findViewById(R.id.l_domain).setVisibility(View.GONE); mEapDomainView.setText(""); } private void setUserCertInvisible() { mView.findViewById(R.id.l_user_cert).setVisibility(View.GONE); setSelection(mEapUserCertSpinner, mUnspecifiedCertString); } private void setAnonymousIdentInvisible() { mView.findViewById(R.id.l_anonymous).setVisibility(View.GONE); mEapAnonymousView.setText(""); } private void setPasswordInvisible() { mPasswordView.setText(""); mView.findViewById(R.id.password_layout).setVisibility(View.GONE); mView.findViewById(R.id.show_password_layout).setVisibility(View.GONE); } private void setEapMethodInvisible() { mView.findViewById(R.id.eap).setVisibility(View.GONE); } private void showIpConfigFields() { WifiConfiguration config = null; mView.findViewById(R.id.ip_fields).setVisibility(View.VISIBLE); if (mWifiEntry != null && mWifiEntry.isSaved()) { config = mWifiEntry.getWifiConfiguration(); } if (mIpSettingsSpinner.getSelectedItemPosition() == STATIC_IP) { mView.findViewById(R.id.staticip).setVisibility(View.VISIBLE); if (mIpAddressView == null) { mIpAddressView = (TextView) mView.findViewById(R.id.ipaddress); mIpAddressView.addTextChangedListener(this); mGatewayView = (TextView) mView.findViewById(R.id.gateway); mGatewayView.addTextChangedListener(getIpConfigFieldsTextWatcher(mGatewayView)); mNetworkPrefixLengthView = (TextView) mView.findViewById( R.id.network_prefix_length); mNetworkPrefixLengthView.addTextChangedListener( getIpConfigFieldsTextWatcher(mNetworkPrefixLengthView)); mDns1View = (TextView) mView.findViewById(R.id.dns1); mDns1View.addTextChangedListener(getIpConfigFieldsTextWatcher(mDns1View)); mDns2View = (TextView) mView.findViewById(R.id.dns2); mDns2View.addTextChangedListener(this); } if (config != null) { StaticIpConfiguration staticConfig = config.getIpConfiguration() .getStaticIpConfiguration(); if (staticConfig != null) { if (staticConfig.getIpAddress() != null) { mIpAddressView.setText( staticConfig.getIpAddress().getAddress().getHostAddress()); mNetworkPrefixLengthView.setText(Integer.toString( staticConfig.getIpAddress().getPrefixLength())); } if (staticConfig.getGateway() != null) { mGatewayView.setText(staticConfig.getGateway().getHostAddress()); } Iterator dnsIterator = staticConfig.getDnsServers().iterator(); if (dnsIterator.hasNext()) { mDns1View.setText(dnsIterator.next().getHostAddress()); } if (dnsIterator.hasNext()) { mDns2View.setText(dnsIterator.next().getHostAddress()); } } } } else { mView.findViewById(R.id.staticip).setVisibility(View.GONE); } } private void showProxyFields() { WifiConfiguration config = null; mView.findViewById(R.id.proxy_settings_fields).setVisibility(View.VISIBLE); if (mWifiEntry != null && mWifiEntry.isSaved()) { config = mWifiEntry.getWifiConfiguration(); } if (mProxySettingsSpinner.getSelectedItemPosition() == PROXY_STATIC) { setVisibility(R.id.proxy_warning_limited_support, View.VISIBLE); setVisibility(R.id.proxy_fields, View.VISIBLE); setVisibility(R.id.proxy_pac_field, View.GONE); if (mProxyHostView == null) { mProxyHostView = (TextView) mView.findViewById(R.id.proxy_hostname); mProxyHostView.addTextChangedListener(this); mProxyPortView = (TextView) mView.findViewById(R.id.proxy_port); mProxyPortView.addTextChangedListener(this); mProxyExclusionListView = (TextView) mView.findViewById(R.id.proxy_exclusionlist); mProxyExclusionListView.addTextChangedListener(this); } if (config != null) { ProxyInfo proxyProperties = config.getHttpProxy(); if (proxyProperties != null) { mProxyHostView.setText(proxyProperties.getHost()); mProxyPortView.setText(Integer.toString(proxyProperties.getPort())); mProxyExclusionListView.setText( ProxyUtils.exclusionListAsString(proxyProperties.getExclusionList())); } } } else if (mProxySettingsSpinner.getSelectedItemPosition() == PROXY_PAC) { setVisibility(R.id.proxy_warning_limited_support, View.GONE); setVisibility(R.id.proxy_fields, View.GONE); setVisibility(R.id.proxy_pac_field, View.VISIBLE); if (mProxyPacView == null) { mProxyPacView = (TextView) mView.findViewById(R.id.proxy_pac); mProxyPacView.addTextChangedListener(this); } if (config != null) { ProxyInfo proxyInfo = config.getHttpProxy(); if (proxyInfo != null) { mProxyPacView.setText(proxyInfo.getPacFileUrl().toString()); } } } else { setVisibility(R.id.proxy_warning_limited_support, View.GONE); setVisibility(R.id.proxy_fields, View.GONE); setVisibility(R.id.proxy_pac_field, View.GONE); } } private void setVisibility(int id, int visibility) { final View v = mView.findViewById(id); if (v != null) { v.setVisibility(visibility); } } @VisibleForTesting AndroidKeystoreAliasLoader getAndroidKeystoreAliasLoader() { return new AndroidKeystoreAliasLoader(KeyProperties.NAMESPACE_WIFI); } @VisibleForTesting void loadSims() { List activeSubscriptionInfos = mContext .getSystemService(SubscriptionManager.class).getActiveSubscriptionInfoList(); if (activeSubscriptionInfos == null) { activeSubscriptionInfos = Collections.EMPTY_LIST; } mActiveSubscriptionInfos.clear(); // De-duplicates active subscriptions and caches in mActiveSubscriptionInfos. for (SubscriptionInfo newInfo : activeSubscriptionInfos) { for (SubscriptionInfo cachedInfo : mActiveSubscriptionInfos) { if (newInfo.getCarrierId() == cachedInfo.getCarrierId()) { continue; } } mActiveSubscriptionInfos.add(newInfo); } // Shows disabled 'No SIM' when there is no active subscription. if (mActiveSubscriptionInfos.size() == 0) { final String[] noSim = new String[]{mContext.getString(R.string.wifi_no_sim_card)}; mEapSimSpinner.setAdapter(getSpinnerAdapter(noSim)); mEapSimSpinner.setSelection(0 /* position */); mEapSimSpinner.setEnabled(false); return; } // Shows display name of each active subscription. final ArrayList displayNames = new ArrayList<>(); for (SubscriptionInfo activeSubInfo : mActiveSubscriptionInfos) { displayNames.add( SubscriptionUtil.getUniqueSubscriptionDisplayName(activeSubInfo, mContext)); } mEapSimSpinner.setAdapter( getSpinnerAdapter(displayNames.toArray(new String[displayNames.size()]))); mEapSimSpinner.setSelection(0 /* position */); if (displayNames.size() == 1) { mEapSimSpinner.setEnabled(false); } } @VisibleForTesting void loadCertificates( Spinner spinner, Collection choices, String noCertificateString, boolean showMultipleCerts, boolean showUsePreinstalledCertOption) { final Context context = mConfigUi.getContext(); ArrayList certs = new ArrayList(); certs.add(mUnspecifiedCertString); if (showMultipleCerts) { certs.add(mMultipleCertSetString); } if (showUsePreinstalledCertOption) { certs.add(mUseSystemCertsString); certs.add(mInstallCertsString); } if (choices != null && choices.size() != 0) { certs.addAll(choices.stream() .filter(certificateName -> { for (String undesired : UNDESIRED_CERTIFICATES) { if (certificateName.startsWith(undesired)) { return false; } } return true; }).collect(Collectors.toList())); } if (!TextUtils.isEmpty(noCertificateString) && mWifiEntrySecurity != WifiEntry.SECURITY_EAP_SUITE_B) { certs.add(noCertificateString); } // If there is only mUnspecifiedCertString and one item to select, only shows the item if (certs.size() == 2) { certs.remove(mUnspecifiedCertString); spinner.setEnabled(false); } else { spinner.setEnabled(true); } final ArrayAdapter adapter = getSpinnerAdapter( certs.toArray(new String[certs.size()])); spinner.setAdapter(adapter); } private void setSelection(Spinner spinner, String value) { if (value != null) { @SuppressWarnings("unchecked") ArrayAdapter adapter = (ArrayAdapter) spinner.getAdapter(); for (int i = adapter.getCount() - 1; i >= 0; --i) { if (value.equals(adapter.getItem(i))) { spinner.setSelection(i); break; } } } } public int getMode() { return mMode; } @Override public void afterTextChanged(Editable s) { ThreadUtils.postOnMainThread(() -> { showWarningMessagesIfAppropriate(); enableSubmitIfAppropriate(); }); } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { // work done in afterTextChanged } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { // work done in afterTextChanged } /* TODO: Add more test cases for this TextWatcher b/186368002 * This TextWatcher is for IP config fields (Gateway/Network Prefix Length/DNS1) to prevent * to rewrite the value in these columns that the user wanted to change after they saved. * When afterTextChanged we will check the text is empty or not then set the Hint for user. */ private TextWatcher getIpConfigFieldsTextWatcher(final TextView view) { return new TextWatcher() { @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { // work done in afterTextChanged } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { // work done in afterTextChanged } @Override public void afterTextChanged(Editable s) { if (s.length() == 0) { if (view.getId() == R.id.gateway) { mGatewayView.setHint(R.string.wifi_gateway_hint); } else if (view.getId() == R.id.network_prefix_length) { mNetworkPrefixLengthView.setHint(R.string.wifi_network_prefix_length_hint); } else if (view.getId() == R.id.dns1) { mDns1View.setHint(R.string.wifi_dns1_hint); } Button submit = mConfigUi.getSubmitButton(); if (submit == null) return; submit.setEnabled(false); } else { ThreadUtils.postOnMainThread(() -> { showWarningMessagesIfAppropriate(); enableSubmitIfAppropriate(); }); } } }; } @Override public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) { if (textView == mPasswordView) { if (id == EditorInfo.IME_ACTION_DONE && isSubmittable()) { mConfigUi.dispatchSubmit(); return true; } } return false; } @Override public boolean onKey(View view, int keyCode, KeyEvent keyEvent) { if (view == mPasswordView) { if (keyCode == KeyEvent.KEYCODE_ENTER && isSubmittable()) { mConfigUi.dispatchSubmit(); return true; } } return false; } @Override public void onCheckedChanged(CompoundButton view, boolean isChecked) { if (view.getId() == R.id.show_password) { int pos = mPasswordView.getSelectionEnd(); mPasswordView.setInputType(InputType.TYPE_CLASS_TEXT | (isChecked ? InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_TEXT_VARIATION_PASSWORD)); if (pos >= 0) { ((EditText) mPasswordView).setSelection(pos); } } else if (view.getId() == R.id.wifi_advanced_togglebox) { // Hide the SoftKeyboard temporary to let user can see most of the expanded items. hideSoftKeyboard(mView.getWindowToken()); view.setVisibility(View.GONE); mView.findViewById(R.id.wifi_advanced_fields).setVisibility(View.VISIBLE); } } @Override public void onItemSelected(AdapterView parent, View view, int position, long id) { if (parent == mSecuritySpinner) { // Convert menu position to actual Wi-Fi security type mWifiEntrySecurity = mSecurityInPosition[position]; showSecurityFields(/* refreshEapMethods */ true, /* refreshCertificates */ true); if (WifiDppUtils.isSupportEnrolleeQrCodeScanner(mContext, mWifiEntrySecurity)) { mSsidScanButton.setVisibility(View.VISIBLE); } else { mSsidScanButton.setVisibility(View.GONE); } } else if (parent == mEapMethodSpinner) { final int selectedItemPosition = mEapMethodSpinner.getSelectedItemPosition(); if (mLastShownEapMethod != selectedItemPosition) { mLastShownEapMethod = selectedItemPosition; showSecurityFields(/* refreshEapMethods */false, /* refreshCertificates */ true); } } else if (parent == mEapCaCertSpinner) { String selectedItem = parent.getItemAtPosition(position).toString(); if (selectedItem.equals(mInstallCertsString)) { startActivityForInstallCerts(); } showSecurityFields(/* refreshEapMethods */ false, /* refreshCertificates */ false); } else if (parent == mPhase2Spinner && mEapMethodSpinner.getSelectedItemPosition() == WIFI_EAP_METHOD_PEAP) { showPeapFields(); } else if (parent == mProxySettingsSpinner) { showProxyFields(); } else if (parent == mHiddenSettingsSpinner) { mHiddenWarningView.setVisibility(position == NOT_HIDDEN_NETWORK ? View.GONE : View.VISIBLE); } else { showIpConfigFields(); } showWarningMessagesIfAppropriate(); enableSubmitIfAppropriate(); } @Override public void onNothingSelected(AdapterView parent) { // } /** * Start the install page for user to install the existing certificate. */ private void startActivityForInstallCerts() { Intent intent = new Intent(ACTION_INSTALL_CERTS); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setComponent(new ComponentName(PACKAGE_INSTALL_CERTS, CLASS_INSTALL_CERTS)); intent.putExtra(KEY_INSTALL_CERTIFICATE, INSTALL_CERTIFICATE_VALUE); mContext.startActivity(intent); } /** * Make the characters of the password visible if show_password is checked. */ public void updatePassword() { TextView passwdView = (TextView) mView.findViewById(R.id.password); passwdView.setInputType(InputType.TYPE_CLASS_TEXT | (((CheckBox) mView.findViewById(R.id.show_password)).isChecked() ? InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD : InputType.TYPE_TEXT_VARIATION_PASSWORD)); } public WifiEntry getWifiEntry() { return mWifiEntry; } private void configureSecuritySpinner() { mConfigUi.setTitle(R.string.wifi_add_network); mSsidView = (TextView) mView.findViewById(R.id.ssid); mSsidView.addTextChangedListener(this); mSecuritySpinner = ((Spinner) mView.findViewById(R.id.security)); mSecuritySpinner.setOnItemSelectedListener(this); ArrayAdapter spinnerAdapter = new ArrayAdapter(mContext, android.R.layout.simple_spinner_item, android.R.id.text1); spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); mSecuritySpinner.setAdapter(spinnerAdapter); int idx = 0; // Populate the Wi-Fi security spinner with the various supported key management types spinnerAdapter.add(mContext.getString(R.string.wifi_security_none)); mSecurityInPosition[idx++] = WifiEntry.SECURITY_NONE; if (mWifiManager.isEnhancedOpenSupported()) { spinnerAdapter.add(mContext.getString(R.string.wifi_security_owe)); mSecurityInPosition[idx++] = WifiEntry.SECURITY_OWE; } spinnerAdapter.add(mContext.getString(R.string.wifi_security_wep)); mSecurityInPosition[idx++] = WifiEntry.SECURITY_WEP; spinnerAdapter.add(mContext.getString(R.string.wifi_security_wpa_wpa2)); mSecurityInPosition[idx++] = WifiEntry.SECURITY_PSK; if (mWifiManager.isWpa3SaeSupported()) { spinnerAdapter.add(mContext.getString(R.string.wifi_security_sae)); mSecurityInPosition[idx++] = WifiEntry.SECURITY_SAE; spinnerAdapter.add(mContext.getString(R.string.wifi_security_eap_wpa_wpa2)); mSecurityInPosition[idx++] = WifiEntry.SECURITY_EAP; spinnerAdapter.add(mContext.getString(R.string.wifi_security_eap_wpa3)); mSecurityInPosition[idx++] = WifiEntry.SECURITY_EAP_WPA3_ENTERPRISE; } else { spinnerAdapter.add(mContext.getString(R.string.wifi_security_eap)); mSecurityInPosition[idx++] = WifiEntry.SECURITY_EAP; } if (mWifiManager.isWpa3SuiteBSupported()) { spinnerAdapter.add(mContext.getString(R.string.wifi_security_eap_suiteb)); mSecurityInPosition[idx++] = WifiEntry.SECURITY_EAP_SUITE_B; } spinnerAdapter.notifyDataSetChanged(); mView.findViewById(R.id.type).setVisibility(View.VISIBLE); showIpConfigFields(); showProxyFields(); mView.findViewById(R.id.wifi_advanced_toggle).setVisibility(View.VISIBLE); // Hidden option can be changed only when the user adds a network manually. mView.findViewById(R.id.hidden_settings_field).setVisibility(View.VISIBLE); ((CheckBox) mView.findViewById(R.id.wifi_advanced_togglebox)) .setOnCheckedChangeListener(this); // Set correct accessibility strings. setAdvancedOptionAccessibilityString(); } /** * For each target string in {@code targetStringArray} try to find if it appears in {@code * originalStringArray}, if found then use the corresponding string, which have the same index * of the target string in {@code replacementStringArray}, to replace it. And finally return the * whole new string array back to caller. */ @VisibleForTesting CharSequence[] findAndReplaceTargetStrings(CharSequence[] originalStringArray, CharSequence[] targetStringArray, CharSequence[] replacementStringArray) { // The length of the targetStringArray and replacementStringArray should be the same, each // item in the targetStringArray should have a 1:1 mapping to replacementStringArray, so // just return the original string if the lengths are different. if (targetStringArray.length != replacementStringArray.length) { return originalStringArray; } final CharSequence[] returnEntries = new CharSequence[originalStringArray.length]; for (int i = 0; i < originalStringArray.length; i++) { returnEntries[i] = originalStringArray[i]; for (int j = 0; j < targetStringArray.length; j++) { if (TextUtils.equals(originalStringArray[i], targetStringArray[j])) { returnEntries[i] = replacementStringArray[j]; } } } return returnEntries; } private ArrayAdapter getSpinnerAdapter( int contentStringArrayResId) { return getSpinnerAdapter( mContext.getResources().getStringArray(contentStringArrayResId)); } @VisibleForTesting ArrayAdapter getSpinnerAdapter( String[] contentStringArray) { ArrayAdapter spinnerAdapter = new ArrayAdapter<>(mContext, android.R.layout.simple_spinner_item, contentStringArray); spinnerAdapter.setDropDownViewResource( android.R.layout.simple_spinner_dropdown_item); return spinnerAdapter; } /** * This function is to span the TTS strings to each EAP method items in the * spinner to have detail TTS content for the TTS engine usage. */ private ArrayAdapter getSpinnerAdapterWithEapMethodsTts( int contentStringArrayResId) { final Resources res = mContext.getResources(); CharSequence[] sourceStrings = res.getStringArray( contentStringArrayResId); CharSequence[] targetStrings = res.getStringArray( R.array.wifi_eap_method_target_strings); CharSequence[] ttsStrings = res.getStringArray( R.array.wifi_eap_method_tts_strings); // Replace the target strings with tts strings and save all in a new array. final CharSequence[] newTtsSourceStrings = findAndReplaceTargetStrings( sourceStrings, targetStrings, ttsStrings); // Build new TtsSpan text arrays for TalkBack. final CharSequence[] accessibilityArray = createAccessibleEntries( sourceStrings, newTtsSourceStrings); // Return a new ArrayAdapter with the new TalkBack array. ArrayAdapter spinnerAdapter = new ArrayAdapter<>( mContext, android.R.layout.simple_spinner_item, accessibilityArray); spinnerAdapter.setDropDownViewResource( android.R.layout.simple_spinner_dropdown_item); return spinnerAdapter; } private SpannableString[] createAccessibleEntries(CharSequence[] entries, CharSequence[] contentDescriptions) { final SpannableString[] accessibleEntries = new SpannableString[entries.length]; for (int i = 0; i < entries.length; i++) { accessibleEntries[i] = com.android.settings.Utils.createAccessibleSequence(entries[i], contentDescriptions[i].toString()); } return accessibleEntries; } private void hideSoftKeyboard(IBinder windowToken) { final InputMethodManager inputMethodManager = mContext.getSystemService( InputMethodManager.class); inputMethodManager.hideSoftInputFromWindow(windowToken, 0 /* flags */); } private void setAdvancedOptionAccessibilityString() { final CheckBox advancedToggleBox = mView.findViewById(R.id.wifi_advanced_togglebox); advancedToggleBox.setAccessibilityDelegate(new AccessibilityDelegate() { @Override public void onInitializeAccessibilityNodeInfo( View v, AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(v, info); // To let TalkBack don't pronounce checked/unchecked. info.setCheckable(false /* checkable */); // To let TalkBack don't pronounce CheckBox. info.setClassName(null /* className */); // Customize TalkBack's pronunciation which been appended to "Double-tap to". final AccessibilityAction customClick = new AccessibilityAction( AccessibilityNodeInfo.ACTION_CLICK, mContext.getString(R.string.wifi_advanced_toggle_description_collapsed)); info.addAction(customClick); } }); } }