diff --git a/res/xml/wifi_display_saved_access_points2.xml b/res/xml/wifi_display_saved_access_points2.xml new file mode 100644 index 00000000000..02c732a5b42 --- /dev/null +++ b/res/xml/wifi_display_saved_access_points2.xml @@ -0,0 +1,33 @@ + + + + + + + + + + diff --git a/res/xml/wifi_settings2.xml b/res/xml/wifi_settings2.xml new file mode 100644 index 00000000000..8cd38575618 --- /dev/null +++ b/res/xml/wifi_settings2.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index 0934ba93e09..98c6b87ba3c 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -146,6 +146,7 @@ import com.android.settings.wifi.calling.WifiCallingDisclaimerFragment; import com.android.settings.wifi.calling.WifiCallingSettings; import com.android.settings.wifi.p2p.WifiP2pSettings; import com.android.settings.wifi.savedaccesspoints.SavedAccessPointsWifiSettings; +import com.android.settings.wifi.savedaccesspoints2.SavedAccessPointsWifiSettings2; import com.android.settings.wifi.tether.WifiTetherSettings; public class SettingsGateway { @@ -161,6 +162,7 @@ public class SettingsGateway { WifiSettings2.class.getName(), ConfigureWifiSettings.class.getName(), SavedAccessPointsWifiSettings.class.getName(), + SavedAccessPointsWifiSettings2.class.getName(), TetherSettings.class.getName(), WifiP2pSettings.class.getName(), WifiTetherSettings.class.getName(), diff --git a/src/com/android/settings/panel/WifiPanel.java b/src/com/android/settings/panel/WifiPanel.java index 36ee1178d06..0efa804de03 100644 --- a/src/com/android/settings/panel/WifiPanel.java +++ b/src/com/android/settings/panel/WifiPanel.java @@ -20,12 +20,14 @@ import android.app.settings.SettingsEnums; import android.content.Context; import android.content.Intent; import android.net.Uri; +import android.util.FeatureFlagUtils; import com.android.settings.R; import com.android.settings.SubSettings; import com.android.settings.slices.CustomSliceRegistry; import com.android.settings.slices.SliceBuilderUtils; import com.android.settings.wifi.WifiSettings; +import com.android.settings.wifi.WifiSettings2; import java.util.ArrayList; import java.util.List; @@ -61,11 +63,20 @@ public class WifiPanel implements PanelContent { public Intent getSeeMoreIntent() { final String screenTitle = mContext.getText(R.string.wifi_settings).toString(); - final Intent intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext, - WifiSettings.class.getName(), - null /* key */, - screenTitle, - SettingsEnums.WIFI); + Intent intent; + if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_WIFITRACKER2)) { + intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext, + WifiSettings2.class.getName(), + null /* key */, + screenTitle, + SettingsEnums.WIFI); + } else { + intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext, + WifiSettings.class.getName(), + null /* key */, + screenTitle, + SettingsEnums.WIFI); + } intent.setClassName(mContext.getPackageName(), SubSettings.class.getName()); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); return intent; diff --git a/src/com/android/settings/wifi/WifiConfigController.java b/src/com/android/settings/wifi/WifiConfigController.java index 0ba2a940c24..a3435e644f8 100644 --- a/src/com/android/settings/wifi/WifiConfigController.java +++ b/src/com/android/settings/wifi/WifiConfigController.java @@ -44,6 +44,7 @@ import android.text.InputType; import android.text.SpannableString; import android.text.TextUtils; import android.text.TextWatcher; +import android.util.FeatureFlagUtils; import android.util.Log; import android.view.KeyEvent; import android.view.View; @@ -70,6 +71,7 @@ import androidx.annotation.VisibleForTesting; import com.android.settings.ProxySelector; import com.android.settings.R; import com.android.settings.wifi.details.WifiPrivacyPreferenceController; +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; @@ -289,9 +291,14 @@ public class WifiConfigController implements TextWatcher, ? HIDDEN_NETWORK : NOT_HIDDEN_NETWORK); - final int prefMacValue = - WifiPrivacyPreferenceController.translateMacRandomizedValueToPrefValue( - config.macRandomizationSetting); + int prefMacValue; + if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_WIFITRACKER2)) { + prefMacValue = WifiPrivacyPreferenceController2 + .translateMacRandomizedValueToPrefValue(config.macRandomizationSetting); + } else { + prefMacValue = WifiPrivacyPreferenceController + .translateMacRandomizedValueToPrefValue(config.macRandomizationSetting); + } mPrivacySettingsSpinner.setSelection(prefMacValue); if (config.getIpAssignment() == IpAssignment.STATIC) { @@ -843,9 +850,14 @@ public class WifiConfigController implements TextWatcher, } if (mPrivacySettingsSpinner != null) { - final int macValue = - WifiPrivacyPreferenceController.translatePrefValueToMacRandomizedValue( - mPrivacySettingsSpinner.getSelectedItemPosition()); + int macValue; + if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_WIFITRACKER2)) { + macValue = WifiPrivacyPreferenceController2.translatePrefValueToMacRandomizedValue( + mPrivacySettingsSpinner.getSelectedItemPosition()); + } else { + macValue = WifiPrivacyPreferenceController.translatePrefValueToMacRandomizedValue( + mPrivacySettingsSpinner.getSelectedItemPosition()); + } config.macRandomizationSetting = macValue; } diff --git a/src/com/android/settings/wifi/WifiConnectionPreferenceController.java b/src/com/android/settings/wifi/WifiConnectionPreferenceController.java index 36c4455fef2..2c6feacc34c 100644 --- a/src/com/android/settings/wifi/WifiConnectionPreferenceController.java +++ b/src/com/android/settings/wifi/WifiConnectionPreferenceController.java @@ -18,6 +18,7 @@ package com.android.settings.wifi; import android.content.Context; import android.os.Bundle; +import android.util.FeatureFlagUtils; import androidx.preference.PreferenceGroup; import androidx.preference.PreferenceScreen; @@ -25,6 +26,7 @@ import androidx.preference.PreferenceScreen; import com.android.settings.R; import com.android.settings.core.SubSettingLauncher; import com.android.settings.wifi.details.WifiNetworkDetailsFragment; +import com.android.settings.wifi.details2.WifiNetworkDetailsFragment2; import com.android.settingslib.core.AbstractPreferenceController; import com.android.settingslib.core.lifecycle.Lifecycle; import com.android.settingslib.wifi.AccessPoint; @@ -128,17 +130,31 @@ public class WifiConnectionPreferenceController extends AbstractPreferenceContro mPreference.refresh(); mPreference.setOrder(order); - mPreference.setOnPreferenceClickListener(pref -> { - Bundle args = new Bundle(); - mPreference.getAccessPoint().saveWifiState(args); - new SubSettingLauncher(mPrefContext) - .setTitleRes(R.string.pref_title_network_details) - .setDestination(WifiNetworkDetailsFragment.class.getName()) - .setArguments(args) - .setSourceMetricsCategory(mMetricsCategory) - .launch(); - return true; - }); + if (FeatureFlagUtils.isEnabled(mPrefContext, FeatureFlagUtils.SETTINGS_WIFITRACKER2)) { + mPreference.setOnPreferenceClickListener(pref -> { + Bundle args = new Bundle(); + mPreference.getAccessPoint().saveWifiState(args); + new SubSettingLauncher(mPrefContext) + .setTitleRes(R.string.pref_title_network_details) + .setDestination(WifiNetworkDetailsFragment2.class.getName()) + .setArguments(args) + .setSourceMetricsCategory(mMetricsCategory) + .launch(); + return true; + }); + } else { + mPreference.setOnPreferenceClickListener(pref -> { + Bundle args = new Bundle(); + mPreference.getAccessPoint().saveWifiState(args); + new SubSettingLauncher(mPrefContext) + .setTitleRes(R.string.pref_title_network_details) + .setDestination(WifiNetworkDetailsFragment.class.getName()) + .setArguments(args) + .setSourceMetricsCategory(mMetricsCategory) + .launch(); + return true; + }); + } mPreferenceGroup.addPreference(mPreference); } } diff --git a/src/com/android/settings/wifi/WifiPickerActivity.java b/src/com/android/settings/wifi/WifiPickerActivity.java index a590a0f5c93..adfc7ec4b6f 100644 --- a/src/com/android/settings/wifi/WifiPickerActivity.java +++ b/src/com/android/settings/wifi/WifiPickerActivity.java @@ -16,6 +16,7 @@ package com.android.settings.wifi; import android.content.Intent; +import android.util.FeatureFlagUtils; import androidx.preference.PreferenceFragmentCompat; @@ -24,6 +25,7 @@ import com.android.settings.R; import com.android.settings.SettingsActivity; import com.android.settings.wifi.p2p.WifiP2pSettings; import com.android.settings.wifi.savedaccesspoints.SavedAccessPointsWifiSettings; +import com.android.settings.wifi.savedaccesspoints2.SavedAccessPointsWifiSettings2; public class WifiPickerActivity extends SettingsActivity implements ButtonBarHandler { @@ -39,9 +41,18 @@ public class WifiPickerActivity extends SettingsActivity implements ButtonBarHan @Override protected boolean isValidFragment(String fragmentName) { + boolean isSavedAccessPointsWifiSettings; + if (FeatureFlagUtils.isEnabled(this, FeatureFlagUtils.SETTINGS_WIFITRACKER2)) { + isSavedAccessPointsWifiSettings = + SavedAccessPointsWifiSettings2.class.getName().equals(fragmentName); + } else { + isSavedAccessPointsWifiSettings = + SavedAccessPointsWifiSettings.class.getName().equals(fragmentName); + } + if (WifiSettings.class.getName().equals(fragmentName) || WifiP2pSettings.class.getName().equals(fragmentName) - || SavedAccessPointsWifiSettings.class.getName().equals(fragmentName)) { + || isSavedAccessPointsWifiSettings) { return true; } return false; diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java index 28b668fab33..a5b380ef897 100644 --- a/src/com/android/settings/wifi/WifiSettings.java +++ b/src/com/android/settings/wifi/WifiSettings.java @@ -68,6 +68,7 @@ import com.android.settings.location.ScanningSettings; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.widget.SwitchBarController; import com.android.settings.wifi.details.WifiNetworkDetailsFragment; +import com.android.settings.wifi.details2.WifiNetworkDetailsFragment2; import com.android.settings.wifi.dpp.WifiDppUtils; import com.android.settingslib.RestrictedLockUtils; import com.android.settingslib.RestrictedLockUtilsInternal; @@ -954,12 +955,21 @@ public class WifiSettings extends RestrictedSettingsFragment ? accessPoint.getTitle() : context.getText(R.string.pref_title_network_details); - new SubSettingLauncher(getContext()) - .setTitleText(title) - .setDestination(WifiNetworkDetailsFragment.class.getName()) - .setArguments(pref.getExtras()) - .setSourceMetricsCategory(getMetricsCategory()) - .launch(); + if (FeatureFlagUtils.isEnabled(context, FeatureFlagUtils.SETTINGS_WIFITRACKER2)) { + new SubSettingLauncher(getContext()) + .setTitleText(title) + .setDestination(WifiNetworkDetailsFragment2.class.getName()) + .setArguments(pref.getExtras()) + .setSourceMetricsCategory(getMetricsCategory()) + .launch(); + } else { + new SubSettingLauncher(getContext()) + .setTitleText(title) + .setDestination(WifiNetworkDetailsFragment.class.getName()) + .setArguments(pref.getExtras()) + .setSourceMetricsCategory(getMetricsCategory()) + .launch(); + } } private Network getCurrentWifiNetwork() { diff --git a/src/com/android/settings/wifi/WifiSettings2.java b/src/com/android/settings/wifi/WifiSettings2.java index 2d26cc4c967..f2cd1cfcb0b 100644 --- a/src/com/android/settings/wifi/WifiSettings2.java +++ b/src/com/android/settings/wifi/WifiSettings2.java @@ -190,7 +190,7 @@ public class WifiSettings2 extends RestrictedSettingsFragment } private void addPreferences() { - addPreferencesFromResource(R.xml.wifi_settings); + addPreferencesFromResource(R.xml.wifi_settings2); mConnectedWifiEntryPreferenceCategory = findPreference(PREF_KEY_CONNECTED_ACCESS_POINTS); mWifiEntryPreferenceCategory = findPreference(PREF_KEY_ACCESS_POINTS); diff --git a/src/com/android/settings/wifi/details2/AddDevicePreferenceController2.java b/src/com/android/settings/wifi/details2/AddDevicePreferenceController2.java new file mode 100644 index 00000000000..de831b74d27 --- /dev/null +++ b/src/com/android/settings/wifi/details2/AddDevicePreferenceController2.java @@ -0,0 +1,86 @@ +/* + * 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.details2; + +import android.content.Context; +import android.content.Intent; +import android.net.wifi.WifiManager; +import android.util.Log; + +import androidx.preference.Preference; + +import com.android.settings.core.BasePreferenceController; +import com.android.settings.wifi.dpp.WifiDppUtils; +import com.android.settingslib.wifi.AccessPoint; + +/** + * {@link BasePreferenceController} that launches Wi-Fi Easy Connect configurator flow + */ +public class AddDevicePreferenceController2 extends BasePreferenceController { + + private static final String TAG = "AddDevicePreferenceController2"; + + private static final String KEY_ADD_DEVICE = "add_device_to_network"; + + private AccessPoint mAccessPoint; + private WifiManager mWifiManager; + + public AddDevicePreferenceController2(Context context) { + super(context, KEY_ADD_DEVICE); + + mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + } + + /** + * Initiate with an {@link AccessPoint}. + */ + public AddDevicePreferenceController2 init(AccessPoint accessPoint) { + mAccessPoint = accessPoint; + + return this; + } + + @Override + public int getAvailabilityStatus() { + if (WifiDppUtils.isSupportConfiguratorQrCodeScanner(mContext, mAccessPoint)) { + return AVAILABLE; + } else { + return CONDITIONALLY_UNAVAILABLE; + } + } + + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (KEY_ADD_DEVICE.equals(preference.getKey())) { + WifiDppUtils.showLockScreen(mContext, () -> launchWifiDppConfiguratorQrCodeScanner()); + return true; /* click is handled */ + } + + return false; /* click is not handled */ + } + + private void launchWifiDppConfiguratorQrCodeScanner() { + final Intent intent = WifiDppUtils.getConfiguratorQrCodeScannerIntentOrNull(mContext, + mWifiManager, mAccessPoint); + + if (intent == null) { + Log.e(TAG, "Launch Wi-Fi QR code scanner with a wrong Wi-Fi network!"); + } else { + mContext.startActivity(intent); + } + } +} diff --git a/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java new file mode 100644 index 00000000000..1d6e4574b49 --- /dev/null +++ b/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2.java @@ -0,0 +1,1207 @@ +/* + * 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.details2; + +import static android.net.NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL; +import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; +import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; +import static android.net.NetworkCapabilities.TRANSPORT_WIFI; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.settings.SettingsEnums; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.VectorDrawable; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.NetworkRequest; +import android.net.NetworkUtils; +import android.net.RouteInfo; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.CountDownTimer; +import android.os.Handler; +import android.text.TextUtils; +import android.util.FeatureFlagUtils; +import android.util.Log; +import android.widget.ImageView; +import android.widget.Toast; + +import androidx.annotation.VisibleForTesting; +import androidx.core.text.BidiFormatter; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceFragmentCompat; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.core.FeatureFlags; +import com.android.settings.core.PreferenceControllerMixin; +import com.android.settings.datausage.WifiDataUsageSummaryPreferenceController; +import com.android.settings.widget.EntityHeaderController; +import com.android.settings.wifi.WifiDialog; +import com.android.settings.wifi.WifiDialog.WifiDialogListener; +import com.android.settings.wifi.WifiUtils; +import com.android.settings.wifi.dpp.WifiDppUtils; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.core.lifecycle.LifecycleObserver; +import com.android.settingslib.core.lifecycle.events.OnPause; +import com.android.settingslib.core.lifecycle.events.OnResume; +import com.android.settingslib.widget.ActionButtonsPreference; +import com.android.settingslib.widget.LayoutPreference; +import com.android.settingslib.wifi.AccessPoint; +import com.android.settingslib.wifi.WifiTracker; +import com.android.settingslib.wifi.WifiTrackerFactory; + +import java.net.Inet4Address; +import java.net.Inet6Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.time.Duration; +import java.util.StringJoiner; +import java.util.stream.Collectors; + +/** + * Controller for logic pertaining to displaying Wifi information for the + * {@link WifiNetworkDetailsFragment}. + */ +public class WifiDetailPreferenceController2 extends AbstractPreferenceController + implements PreferenceControllerMixin, WifiDialogListener, LifecycleObserver, OnPause, + OnResume { + + private static final String TAG = "WifiDetailsPrefCtrl2"; + private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG); + + @VisibleForTesting + static final String KEY_HEADER = "connection_header"; + @VisibleForTesting + static final String KEY_DATA_USAGE_HEADER = "status_header"; + @VisibleForTesting + static final String KEY_BUTTONS_PREF = "buttons"; + @VisibleForTesting + static final String KEY_SIGNAL_STRENGTH_PREF = "signal_strength"; + @VisibleForTesting + static final String KEY_TX_LINK_SPEED = "tx_link_speed"; + @VisibleForTesting + static final String KEY_RX_LINK_SPEED = "rx_link_speed"; + @VisibleForTesting + static final String KEY_FREQUENCY_PREF = "frequency"; + @VisibleForTesting + static final String KEY_SECURITY_PREF = "security"; + @VisibleForTesting + static final String KEY_SSID_PREF = "ssid"; + @VisibleForTesting + static final String KEY_MAC_ADDRESS_PREF = "mac_address"; + @VisibleForTesting + static final String KEY_IP_ADDRESS_PREF = "ip_address"; + @VisibleForTesting + static final String KEY_GATEWAY_PREF = "gateway"; + @VisibleForTesting + static final String KEY_SUBNET_MASK_PREF = "subnet_mask"; + @VisibleForTesting + static final String KEY_DNS_PREF = "dns"; + @VisibleForTesting + static final String KEY_IPV6_CATEGORY = "ipv6_category"; + @VisibleForTesting + static final String KEY_IPV6_ADDRESSES_PREF = "ipv6_addresses"; + + private static final int STATE_NONE = 1; + private static final int STATE_ENABLE_WIFI = 2; + private static final int STATE_ENABLE_WIFI_FAILED = 3; + private static final int STATE_CONNECTING = 4; + private static final int STATE_CONNECTED = 5; + private static final int STATE_FAILED = 6; + private static final int STATE_NOT_IN_RANGE = 7; + private static final int STATE_DISCONNECTED = 8; + private static final long TIMEOUT = Duration.ofSeconds(10).toMillis(); + + // Be static to avoid too much object not be reset. + @VisibleForTesting + static CountDownTimer sTimer; + + private AccessPoint mAccessPoint; + private final ConnectivityManager mConnectivityManager; + private final PreferenceFragmentCompat mFragment; + private final Handler mHandler; + private LinkProperties mLinkProperties; + private Network mNetwork; + private NetworkInfo mNetworkInfo; + private NetworkCapabilities mNetworkCapabilities; + private int mRssiSignalLevel = -1; + private String[] mSignalStr; + private WifiConfiguration mWifiConfig; + private WifiInfo mWifiInfo; + private final WifiManager mWifiManager; + private final WifiTracker mWifiTracker; + private final MetricsFeatureProvider mMetricsFeatureProvider; + private boolean mIsOutOfRange; + private boolean mIsEphemeral; + private boolean mConnected; + private int mConnectingState; + private WifiManager.ActionListener mConnectListener; + + // UI elements - in order of appearance + private ActionButtonsPreference mButtonsPref; + private EntityHeaderController mEntityHeaderController; + private Preference mSignalStrengthPref; + private Preference mTxLinkSpeedPref; + private Preference mRxLinkSpeedPref; + private Preference mFrequencyPref; + private Preference mSecurityPref; + private Preference mSsidPref; + private Preference mMacAddressPref; + private Preference mIpAddressPref; + private Preference mGatewayPref; + private Preference mSubnetPref; + private Preference mDnsPref; + private PreferenceCategory mIpv6Category; + private Preference mIpv6AddressPref; + private Lifecycle mLifecycle; + Preference mDataUsageSummaryPref; + WifiDataUsageSummaryPreferenceController mSummaryHeaderController; + + private final IconInjector mIconInjector; + private final IntentFilter mFilter; + + // Passpoint information - cache it in case of losing these information after + // updateAccessPointFromScannedList(). For R2, we should update these data from + // WifiManager#getPasspointConfigurations() after users manage the passpoint profile. + private boolean mIsExpired; + private boolean mIsPasspointConfigurationR1; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + switch (intent.getAction()) { + case WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION: + if (!intent.getBooleanExtra(WifiManager.EXTRA_MULTIPLE_NETWORKS_CHANGED, + false /* defaultValue */)) { + // only one network changed + WifiConfiguration wifiConfiguration = intent + .getParcelableExtra(WifiManager.EXTRA_WIFI_CONFIGURATION); + if (mAccessPoint.matches(wifiConfiguration)) { + mWifiConfig = wifiConfiguration; + } + } + // fall through + case WifiManager.NETWORK_STATE_CHANGED_ACTION: + case WifiManager.RSSI_CHANGED_ACTION: + refreshPage(); + break; + } + } + }; + + private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder() + .clearCapabilities().addTransportType(TRANSPORT_WIFI).build(); + + // Must be run on the UI thread since it directly manipulates UI state. + private final NetworkCallback mNetworkCallback = new NetworkCallback() { + @Override + public void onLinkPropertiesChanged(Network network, LinkProperties lp) { + if (network.equals(mNetwork) && !lp.equals(mLinkProperties)) { + mLinkProperties = lp; + refreshIpLayerInfo(); + } + } + + private boolean hasCapabilityChanged(NetworkCapabilities nc, int cap) { + // If this is the first time we get NetworkCapabilities, report that something changed. + if (mNetworkCapabilities == null) return true; + + // nc can never be null, see ConnectivityService#callCallbackForRequest. + return mNetworkCapabilities.hasCapability(cap) != nc.hasCapability(cap); + } + + private boolean hasPrivateDnsStatusChanged(NetworkCapabilities nc) { + // If this is the first time that WifiDetailPreferenceController2 gets + // NetworkCapabilities, report that something has changed and assign nc to + // mNetworkCapabilities in onCapabilitiesChanged. Note that the NetworkCapabilities + // from onCapabilitiesChanged() will never be null, so calling + // mNetworkCapabilities.isPrivateDnsBroken() would be safe next time. + if (mNetworkCapabilities == null) { + return true; + } + + return mNetworkCapabilities.isPrivateDnsBroken() != nc.isPrivateDnsBroken(); + } + + @Override + public void onCapabilitiesChanged(Network network, NetworkCapabilities nc) { + // If the network just validated or lost Internet access or detected partial internet + // connectivity or private dns was broken, refresh network state. Don't do this on + // every NetworkCapabilities change because refreshEntityHeader sends IPCs to the + // system server from the UI thread, which can cause jank. + if (network.equals(mNetwork) && !nc.equals(mNetworkCapabilities)) { + if (hasPrivateDnsStatusChanged(nc) + || hasCapabilityChanged(nc, NET_CAPABILITY_VALIDATED) + || hasCapabilityChanged(nc, NET_CAPABILITY_CAPTIVE_PORTAL) + || hasCapabilityChanged(nc, NET_CAPABILITY_PARTIAL_CONNECTIVITY)) { + mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo); + refreshEntityHeader(); + } + mNetworkCapabilities = nc; + refreshButtons(); + refreshIpLayerInfo(); + } + } + + @Override + public void onLost(Network network) { + // Ephemeral network not a saved network, leave detail page once disconnected + if (mIsEphemeral && network.equals(mNetwork)) { + exitActivity(); + } + } + }; + + @VisibleForTesting + final WifiTracker.WifiListener mWifiListener = new WifiTracker.WifiListener() { + /** Called when the state of Wifi has changed. */ + public void onWifiStateChanged(int state) { + Log.d(TAG, "onWifiStateChanged(" + state + ")"); + if (mConnectingState == STATE_ENABLE_WIFI && state == WifiManager.WIFI_STATE_ENABLED) { + updateConnectingState(STATE_CONNECTING); + } else if (mConnectingState != STATE_NONE && state == WifiManager.WIFI_STATE_DISABLED) { + // update as disconnected once Wi-Fi disabled since may not received + // onConnectedChanged for this case. + updateConnectingState(STATE_DISCONNECTED); + } + } + + /** Called when the connection state of wifi has changed. */ + public void onConnectedChanged() { + refreshPage(); + } + + /** + * Called to indicate the list of AccessPoints has been updated and + * {@link WifiTracker#getAccessPoints()} should be called to get the updated list. + */ + public void onAccessPointsChanged() { + refreshPage(); + } + }; + + /** + * To get an instance of {@link WifiDetailPreferenceController2} + */ + public static WifiDetailPreferenceController2 newInstance( + AccessPoint accessPoint, + ConnectivityManager connectivityManager, + Context context, + PreferenceFragmentCompat fragment, + Handler handler, + Lifecycle lifecycle, + WifiManager wifiManager, + MetricsFeatureProvider metricsFeatureProvider) { + return new WifiDetailPreferenceController2( + accessPoint, connectivityManager, context, fragment, handler, lifecycle, + wifiManager, metricsFeatureProvider, new IconInjector(context)); + } + + @VisibleForTesting + /* package */ WifiDetailPreferenceController2( + AccessPoint accessPoint, + ConnectivityManager connectivityManager, + Context context, + PreferenceFragmentCompat fragment, + Handler handler, + Lifecycle lifecycle, + WifiManager wifiManager, + MetricsFeatureProvider metricsFeatureProvider, + IconInjector injector) { + super(context); + + mAccessPoint = accessPoint; + mConnectivityManager = connectivityManager; + mFragment = fragment; + mHandler = handler; + mSignalStr = context.getResources().getStringArray(R.array.wifi_signal); + mWifiConfig = accessPoint.getConfig(); + mWifiManager = wifiManager; + mMetricsFeatureProvider = metricsFeatureProvider; + mIconInjector = injector; + + mFilter = new IntentFilter(); + mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); + mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); + mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION); + + mLifecycle = lifecycle; + lifecycle.addObserver(this); + + mWifiTracker = WifiTrackerFactory.create( + mFragment.getActivity(), + mWifiListener, + mLifecycle, + true /*includeSaved*/, + true /*includeScans*/); + mConnected = mAccessPoint.isActive(); + // When lost the network connection, WifiInfo/NetworkInfo will be clear. So causes we + // could not check if the AccessPoint is ephemeral. Need to cache it in first. + mIsEphemeral = mAccessPoint.isEphemeral(); + mConnectingState = STATE_NONE; + mConnectListener = new WifiManager.ActionListener() { + @Override + public void onSuccess() { + // Do nothing + } + + @Override + public void onFailure(int reason) { + updateConnectingState(STATE_FAILED); + } + }; + + mIsExpired = mAccessPoint.isExpired(); + mIsPasspointConfigurationR1 = mAccessPoint.isPasspointConfigurationR1(); + } + + @Override + public boolean isAvailable() { + return true; + } + + @Override + public String getPreferenceKey() { + // Returns null since this controller contains more than one Preference + return null; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + + setupEntityHeader(screen); + + mButtonsPref = ((ActionButtonsPreference) screen.findPreference(KEY_BUTTONS_PREF)) + .setButton1Text(R.string.forget) + .setButton1Icon(R.drawable.ic_settings_delete) + .setButton1OnClickListener(view -> forgetNetwork()) + .setButton2Text(R.string.wifi_sign_in_button_text) + .setButton2Icon(R.drawable.ic_settings_sign_in) + .setButton2OnClickListener(view -> signIntoNetwork()) + .setButton3Text(R.string.wifi_connect) + .setButton3Icon(R.drawable.ic_settings_wireless) + .setButton3OnClickListener(view -> connectNetwork()) + .setButton3Enabled(true) + .setButton4Text(R.string.share) + .setButton4Icon(R.drawable.ic_qrcode_24dp) + .setButton4OnClickListener(view -> shareNetwork()); + + if (isPasspointConfigurationR1Expired()) { + // Hide Connect button. + mButtonsPref.setButton3Visible(false); + } + + mSignalStrengthPref = screen.findPreference(KEY_SIGNAL_STRENGTH_PREF); + mTxLinkSpeedPref = screen.findPreference(KEY_TX_LINK_SPEED); + mRxLinkSpeedPref = screen.findPreference(KEY_RX_LINK_SPEED); + mFrequencyPref = screen.findPreference(KEY_FREQUENCY_PREF); + mSecurityPref = screen.findPreference(KEY_SECURITY_PREF); + + mSsidPref = screen.findPreference(KEY_SSID_PREF); + mMacAddressPref = screen.findPreference(KEY_MAC_ADDRESS_PREF); + mIpAddressPref = screen.findPreference(KEY_IP_ADDRESS_PREF); + mGatewayPref = screen.findPreference(KEY_GATEWAY_PREF); + mSubnetPref = screen.findPreference(KEY_SUBNET_MASK_PREF); + mDnsPref = screen.findPreference(KEY_DNS_PREF); + + mIpv6Category = screen.findPreference(KEY_IPV6_CATEGORY); + mIpv6AddressPref = screen.findPreference(KEY_IPV6_ADDRESSES_PREF); + + mSecurityPref.setSummary(mAccessPoint.getSecurityString(/* concise */ false)); + } + + private void setupEntityHeader(PreferenceScreen screen) { + LayoutPreference headerPref = screen.findPreference(KEY_HEADER); + + if (usingDataUsageHeader(mContext)) { + headerPref.setVisible(false); + mDataUsageSummaryPref = screen.findPreference(KEY_DATA_USAGE_HEADER); + mDataUsageSummaryPref.setVisible(true); + mSummaryHeaderController = + new WifiDataUsageSummaryPreferenceController(mFragment.getActivity(), + mLifecycle, (PreferenceFragmentCompat) mFragment, mAccessPoint.getSsid()); + return; + } + + mEntityHeaderController = + EntityHeaderController.newInstance( + mFragment.getActivity(), mFragment, + headerPref.findViewById(R.id.entity_header)); + + ImageView iconView = headerPref.findViewById(R.id.entity_header_icon); + + iconView.setScaleType(ImageView.ScaleType.CENTER_INSIDE); + + mEntityHeaderController.setLabel(mAccessPoint.getTitle()); + } + + private void refreshEntityHeader() { + if (usingDataUsageHeader(mContext)) { + mSummaryHeaderController.updateState(mDataUsageSummaryPref); + } else { + String summary; + if (isPasspointConfigurationR1Expired()) { + // Not able to get summary from AccessPoint because we may lost + // PasspointConfiguration information after updateAccessPointFromScannedList(). + summary = mContext.getResources().getString( + com.android.settingslib.R.string.wifi_passpoint_expired); + } else { + summary = mAccessPoint.getSettingsSummary(true /* convertSavedAsDisconnected */); + } + + mEntityHeaderController + .setSummary(summary) + .setRecyclerView(mFragment.getListView(), mLifecycle) + .done(mFragment.getActivity(), true /* rebind */); + } + } + + private void updateNetworkInfo() { + mNetwork = mWifiManager.getCurrentNetwork(); + mLinkProperties = mConnectivityManager.getLinkProperties(mNetwork); + mNetworkCapabilities = mConnectivityManager.getNetworkCapabilities(mNetwork); + } + + @Override + public void onResume() { + // Ensure mNetwork is set before any callbacks above are delivered, since our + // NetworkCallback only looks at changes to mNetwork. + updateNetworkInfo(); + refreshPage(); + mContext.registerReceiver(mReceiver, mFilter); + mConnectivityManager.registerNetworkCallback(mNetworkRequest, mNetworkCallback, + mHandler); + } + + @Override + public void onPause() { + mNetwork = null; + mLinkProperties = null; + mNetworkCapabilities = null; + mNetworkInfo = null; + mWifiInfo = null; + mContext.unregisterReceiver(mReceiver); + mConnectivityManager.unregisterNetworkCallback(mNetworkCallback); + } + + private void refreshPage() { + if (!updateAccessPoint()) { + return; + } + + Log.d(TAG, "Update UI!"); + + // refresh header + refreshEntityHeader(); + + // refresh Buttons + refreshButtons(); + + // Update Connection Header icon and Signal Strength Preference + refreshRssiViews(); + // Frequency Pref + refreshFrequency(); + // Transmit Link Speed Pref + refreshTxSpeed(); + // Receive Link Speed Pref + refreshRxSpeed(); + // IP related information + refreshIpLayerInfo(); + // SSID Pref + refreshSsid(); + // MAC Address Pref + refreshMacAddress(); + } + + @VisibleForTesting + boolean updateAccessPoint() { + boolean changed = false; + // remember mIsOutOfRange as old before updated + boolean oldState = mIsOutOfRange; + updateAccessPointFromScannedList(); + + if (mAccessPoint.isActive()) { + updateNetworkInfo(); + mNetworkInfo = mConnectivityManager.getNetworkInfo(mNetwork); + mWifiInfo = mWifiManager.getConnectionInfo(); + if (mNetwork == null || mNetworkInfo == null || mWifiInfo == null) { + // Once connected, can't get mNetwork immediately, return false and wait for + // next time to update UI. also reset {@code mIsOutOfRange} + mIsOutOfRange = oldState; + return false; + } + changed |= mAccessPoint.update(mWifiConfig, mWifiInfo, mNetworkInfo); + } + + // signal level changed + changed |= mRssiSignalLevel != mAccessPoint.getLevel(); + // In/Out of range changed + changed |= oldState != mIsOutOfRange; + // connect state changed + if (mConnected != mAccessPoint.isActive()) { + mConnected = mAccessPoint.isActive(); + changed = true; + updateConnectingState(mAccessPoint.isActive() ? STATE_CONNECTED : STATE_DISCONNECTED); + } + + return changed; + } + + private void updateAccessPointFromScannedList() { + mIsOutOfRange = true; + + for (AccessPoint ap : mWifiTracker.getAccessPoints()) { + if (mAccessPoint.matches(ap)) { + mAccessPoint = ap; + mWifiConfig = ap.getConfig(); + mIsOutOfRange = !mAccessPoint.isReachable(); + return; + } + } + } + + private void exitActivity() { + if (DEBUG) { + Log.d(TAG, "Exiting the WifiNetworkDetailsPage"); + } + mFragment.getActivity().finish(); + } + + private void refreshRssiViews() { + int signalLevel = mAccessPoint.getLevel(); + + // Disappears signal view if not in range. e.g. for saved networks. + if (mIsOutOfRange) { + mSignalStrengthPref.setVisible(false); + mRssiSignalLevel = -1; + return; + } + + if (mRssiSignalLevel == signalLevel) { + return; + } + mRssiSignalLevel = signalLevel; + Drawable wifiIcon = mIconInjector.getIcon(mRssiSignalLevel); + + if (mEntityHeaderController != null) { + mEntityHeaderController + .setIcon(redrawIconForHeader(wifiIcon)).done(mFragment.getActivity(), + true /* rebind */); + } + + Drawable wifiIconDark = wifiIcon.getConstantState().newDrawable().mutate(); + wifiIconDark.setTintList(Utils.getColorAttr(mContext, android.R.attr.colorControlNormal)); + mSignalStrengthPref.setIcon(wifiIconDark); + + mSignalStrengthPref.setSummary(mSignalStr[mRssiSignalLevel]); + mSignalStrengthPref.setVisible(true); + } + + private Drawable redrawIconForHeader(Drawable original) { + final int iconSize = mContext.getResources().getDimensionPixelSize( + R.dimen.wifi_detail_page_header_image_size); + final int actualWidth = original.getMinimumWidth(); + final int actualHeight = original.getMinimumHeight(); + + if ((actualWidth == iconSize && actualHeight == iconSize) + || !VectorDrawable.class.isInstance(original)) { + return original; + } + + // clear tint list to make sure can set 87% black after enlarge + original.setTintList(null); + + // enlarge icon size + final Bitmap bitmap = Utils.createBitmap(original, + iconSize /*width*/, + iconSize /*height*/); + Drawable newIcon = new BitmapDrawable(null /*resource*/, bitmap); + + // config color for 87% black after enlarge + newIcon.setTintList(Utils.getColorAttr(mContext, android.R.attr.textColorPrimary)); + + return newIcon; + } + + private void refreshFrequency() { + if (mWifiInfo == null) { + mFrequencyPref.setVisible(false); + return; + } + + final int frequency = mWifiInfo.getFrequency(); + String band = null; + if (frequency >= AccessPoint.LOWER_FREQ_24GHZ + && frequency < AccessPoint.HIGHER_FREQ_24GHZ) { + band = mContext.getResources().getString(R.string.wifi_band_24ghz); + } else if (frequency >= AccessPoint.LOWER_FREQ_5GHZ + && frequency < AccessPoint.HIGHER_FREQ_5GHZ) { + band = mContext.getResources().getString(R.string.wifi_band_5ghz); + } else { + Log.e(TAG, "Unexpected frequency " + frequency); + // Connecting state is unstable, make it disappeared if unexpected + if (mConnectingState == STATE_CONNECTING) { + mFrequencyPref.setVisible(false); + } + return; + } + mFrequencyPref.setSummary(band); + mFrequencyPref.setVisible(true); + } + + private void refreshTxSpeed() { + if (mWifiInfo == null) { + mTxLinkSpeedPref.setVisible(false); + return; + } + + int txLinkSpeedMbps = mWifiInfo.getTxLinkSpeedMbps(); + mTxLinkSpeedPref.setVisible(txLinkSpeedMbps >= 0); + mTxLinkSpeedPref.setSummary(mContext.getString( + R.string.tx_link_speed, mWifiInfo.getTxLinkSpeedMbps())); + } + + private void refreshRxSpeed() { + if (mWifiInfo == null) { + mRxLinkSpeedPref.setVisible(false); + return; + } + + int rxLinkSpeedMbps = mWifiInfo.getRxLinkSpeedMbps(); + mRxLinkSpeedPref.setVisible(rxLinkSpeedMbps >= 0); + mRxLinkSpeedPref.setSummary(mContext.getString( + R.string.rx_link_speed, mWifiInfo.getRxLinkSpeedMbps())); + } + + private void refreshSsid() { + if (mAccessPoint.isPasspoint() || mAccessPoint.isOsuProvider()) { + mSsidPref.setVisible(true); + mSsidPref.setSummary(mAccessPoint.getSsidStr()); + } else { + mSsidPref.setVisible(false); + } + } + + private void refreshMacAddress() { + String macAddress = getMacAddress(); + if (macAddress == null) { + mMacAddressPref.setVisible(false); + return; + } + + mMacAddressPref.setVisible(true); + if (macAddress.equals(WifiInfo.DEFAULT_MAC_ADDRESS)) { + mMacAddressPref.setSummary(R.string.device_info_not_available); + } else { + mMacAddressPref.setSummary(macAddress); + } + + // MAC Address Pref Title + refreshMacTitle(); + } + + private String getMacAddress() { + if (mWifiInfo != null) { + // get MAC address from connected network information + return mWifiInfo.getMacAddress(); + } + + // return randomized MAC address + if (mWifiConfig != null && mWifiConfig.macRandomizationSetting + == WifiConfiguration.RANDOMIZATION_PERSISTENT) { + return mWifiConfig.getRandomizedMacAddress().toString(); + } + + // return device MAC address + final String[] macAddresses = mWifiManager.getFactoryMacAddresses(); + if (macAddresses != null && macAddresses.length > 0) { + return macAddresses[0]; + } + + Log.e(TAG, "Can't get device MAC address!"); + return null; + } + + private void updatePreference(Preference pref, String detailText) { + if (!TextUtils.isEmpty(detailText)) { + pref.setSummary(detailText); + pref.setVisible(true); + } else { + pref.setVisible(false); + } + } + + private void refreshButtons() { + // Ephemeral network won't be removed permanently, but be putted in blacklist. + mButtonsPref.setButton1Text( + mIsEphemeral ? R.string.wifi_disconnect_button_text : R.string.forget); + + boolean canForgetNetwork = canForgetNetwork(); + boolean canSignIntoNetwork = canSignIntoNetwork(); + boolean canConnectNetwork = canConnectNetwork() && !isPasspointConfigurationR1Expired(); + boolean canShareNetwork = canShareNetwork(); + + mButtonsPref.setButton1Visible(canForgetNetwork); + mButtonsPref.setButton2Visible(canSignIntoNetwork); + mButtonsPref.setButton3Visible(canConnectNetwork); + mButtonsPref.setButton4Visible(canShareNetwork); + mButtonsPref.setVisible(canForgetNetwork + || canSignIntoNetwork + || canConnectNetwork + || canShareNetwork); + } + + private boolean canConnectNetwork() { + // Display connect button for disconnected AP even not in the range. + return !mAccessPoint.isActive(); + } + + private boolean isPasspointConfigurationR1Expired() { + return mIsPasspointConfigurationR1 && mIsExpired; + } + + private void refreshIpLayerInfo() { + // Hide IP layer info if not a connected network. + if (!mAccessPoint.isActive() || mNetwork == null || mLinkProperties == null) { + mIpAddressPref.setVisible(false); + mSubnetPref.setVisible(false); + mGatewayPref.setVisible(false); + mDnsPref.setVisible(false); + mIpv6Category.setVisible(false); + return; + } + + // Find IPv4 and IPv6 addresses. + String ipv4Address = null; + String subnet = null; + StringJoiner ipv6Addresses = new StringJoiner("\n"); + + for (LinkAddress addr : mLinkProperties.getLinkAddresses()) { + if (addr.getAddress() instanceof Inet4Address) { + ipv4Address = addr.getAddress().getHostAddress(); + subnet = ipv4PrefixLengthToSubnetMask(addr.getPrefixLength()); + } else if (addr.getAddress() instanceof Inet6Address) { + ipv6Addresses.add(addr.getAddress().getHostAddress()); + } + } + + // Find IPv4 default gateway. + String gateway = null; + for (RouteInfo routeInfo : mLinkProperties.getRoutes()) { + if (routeInfo.isIPv4Default() && routeInfo.hasGateway()) { + gateway = routeInfo.getGateway().getHostAddress(); + break; + } + } + + // Find all (IPv4 and IPv6) DNS addresses. + String dnsServers = mLinkProperties.getDnsServers().stream() + .map(InetAddress::getHostAddress) + .collect(Collectors.joining("\n")); + + // Update UI. + updatePreference(mIpAddressPref, ipv4Address); + updatePreference(mSubnetPref, subnet); + updatePreference(mGatewayPref, gateway); + updatePreference(mDnsPref, dnsServers); + + if (ipv6Addresses.length() > 0) { + mIpv6AddressPref.setSummary( + BidiFormatter.getInstance().unicodeWrap(ipv6Addresses.toString())); + mIpv6Category.setVisible(true); + } else { + mIpv6Category.setVisible(false); + } + } + + private static String ipv4PrefixLengthToSubnetMask(int prefixLength) { + try { + InetAddress all = InetAddress.getByAddress( + new byte[]{(byte) 255, (byte) 255, (byte) 255, (byte) 255}); + return NetworkUtils.getNetworkPart(all, prefixLength).getHostAddress(); + } catch (UnknownHostException e) { + return null; + } + } + + /** + * Returns whether the network represented by this preference can be forgotten. + */ + private boolean canForgetNetwork() { + return (mWifiInfo != null && mWifiInfo.isEphemeral()) || canModifyNetwork() + || mAccessPoint.isPasspoint() || mAccessPoint.isPasspointConfig(); + } + + /** + * Returns whether the network represented by this preference can be modified. + */ + public boolean canModifyNetwork() { + return mWifiConfig != null && !WifiUtils.isNetworkLockedDown(mContext, mWifiConfig); + } + + /** + * Returns whether the user can sign into the network represented by this preference. + */ + private boolean canSignIntoNetwork() { + return mAccessPoint.isActive() && WifiUtils.canSignIntoNetwork(mNetworkCapabilities); + } + + /** + * Returns whether the user can share the network represented by this preference with QR code. + */ + private boolean canShareNetwork() { + return mAccessPoint.getConfig() != null + && WifiDppUtils.isSupportConfiguratorQrCodeGenerator(mContext, mAccessPoint); + } + + /** + * Forgets the wifi network associated with this preference. + */ + private void forgetNetwork() { + if (mWifiInfo != null && mWifiInfo.isEphemeral()) { + mWifiManager.disableEphemeralNetwork(mWifiInfo.getSSID()); + } else if (mAccessPoint.isPasspoint() || mAccessPoint.isPasspointConfig()) { + // Post a dialog to confirm if user really want to forget the passpoint network. + showConfirmForgetDialog(); + return; + } else if (mWifiConfig != null) { + mWifiManager.forget(mWifiConfig.networkId, null /* action listener */); + } + + mMetricsFeatureProvider.action( + mFragment.getActivity(), SettingsEnums.ACTION_WIFI_FORGET); + mFragment.getActivity().finish(); + } + + @VisibleForTesting + protected void showConfirmForgetDialog() { + final AlertDialog dialog = new AlertDialog.Builder(mContext) + .setPositiveButton(R.string.forget, ((dialog1, which) -> { + try { + mWifiManager.removePasspointConfiguration(mAccessPoint.getPasspointFqdn()); + } catch (RuntimeException e) { + Log.e(TAG, "Failed to remove Passpoint configuration for " + + mAccessPoint.getPasspointFqdn()); + } + mMetricsFeatureProvider.action( + mFragment.getActivity(), SettingsEnums.ACTION_WIFI_FORGET); + mFragment.getActivity().finish(); + })) + .setNegativeButton(R.string.cancel, null /* listener */) + .setTitle(R.string.wifi_forget_dialog_title) + .setMessage(R.string.forget_passpoint_dialog_message) + .create(); + dialog.show(); + } + + /** + * Show QR code to share the network represented by this preference. + */ + private void launchWifiDppConfiguratorActivity() { + final Intent intent = WifiDppUtils.getConfiguratorQrCodeGeneratorIntentOrNull(mContext, + mWifiManager, mAccessPoint); + + if (intent == null) { + Log.e(TAG, "Launch Wi-Fi DPP QR code generator with a wrong Wi-Fi network!"); + } else { + mMetricsFeatureProvider.action(SettingsEnums.PAGE_UNKNOWN, + SettingsEnums.ACTION_SETTINGS_SHARE_WIFI_QR_CODE, + SettingsEnums.SETTINGS_WIFI_DPP_CONFIGURATOR, + /* key */ null, + /* value */ Integer.MIN_VALUE); + + mContext.startActivity(intent); + } + } + + /** + * Share the wifi network with QR code. + */ + private void shareNetwork() { + WifiDppUtils.showLockScreen(mContext, () -> launchWifiDppConfiguratorActivity()); + } + + /** + * Sign in to the captive portal found on this wifi network associated with this preference. + */ + private void signIntoNetwork() { + mMetricsFeatureProvider.action( + mFragment.getActivity(), SettingsEnums.ACTION_WIFI_SIGNIN); + mConnectivityManager.startCaptivePortalApp(mNetwork); + } + + @Override + public void onSubmit(WifiDialog dialog) { + if (dialog.getController() != null) { + mWifiManager.save(dialog.getController().getConfig(), new WifiManager.ActionListener() { + @Override + public void onSuccess() { + } + + @Override + public void onFailure(int reason) { + Activity activity = mFragment.getActivity(); + if (activity != null) { + Toast.makeText(activity, + R.string.wifi_failed_save_message, + Toast.LENGTH_SHORT).show(); + } + } + }); + } + } + + /** + * Wrapper for testing compatibility. + */ + @VisibleForTesting + static class IconInjector { + private final Context mContext; + + IconInjector(Context context) { + mContext = context; + } + + public Drawable getIcon(int level) { + return mContext.getDrawable(Utils.getWifiIconResource(level)).mutate(); + } + } + + private boolean usingDataUsageHeader(Context context) { + return FeatureFlagUtils.isEnabled(context, FeatureFlags.WIFI_DETAILS_DATAUSAGE_HEADER); + } + + @VisibleForTesting + void connectNetwork() { + final Activity activity = mFragment.getActivity(); + // error handling, connected/saved network should have mWifiConfig. + if (mWifiConfig == null) { + Toast.makeText(activity, + R.string.wifi_failed_connect_message, + Toast.LENGTH_SHORT).show(); + return; + } + + // init state before connect + mConnectingState = STATE_NONE; + + if (mWifiManager.isWifiEnabled()) { + updateConnectingState(STATE_CONNECTING); + } else { + // Enable Wi-Fi automatically to connect AP + updateConnectingState(STATE_ENABLE_WIFI); + } + } + + private void updateConnectingState(int state) { + final Activity activity = mFragment.getActivity(); + Log.d(TAG, "updateConnectingState from " + mConnectingState + " to " + state); + switch (mConnectingState) { + case STATE_NONE: + case STATE_ENABLE_WIFI: + if (state == STATE_ENABLE_WIFI) { + Log.d(TAG, "Turn on Wi-Fi automatically!"); + updateConnectedButton(STATE_ENABLE_WIFI); + Toast.makeText(activity, + R.string.wifi_turned_on_message, + Toast.LENGTH_SHORT).show(); + mWifiManager.setWifiEnabled(true); + // start timer for error handling + startTimer(); + } else if (state == STATE_CONNECTING) { + Log.d(TAG, "connecting..."); + updateConnectedButton(STATE_CONNECTING); + if (mAccessPoint.isPasspoint()) { + mWifiManager.connect(mWifiConfig, mConnectListener); + } else { + mWifiManager.connect(mWifiConfig.networkId, mConnectListener); + } + // start timer for error handling since framework didn't call back if failed + startTimer(); + } else if (state == STATE_ENABLE_WIFI_FAILED) { + Log.e(TAG, "Wi-Fi failed to enable network!"); + stopTimer(); + // reset state + state = STATE_NONE; + Toast.makeText(activity, + R.string.wifi_failed_connect_message, + Toast.LENGTH_SHORT).show(); + updateConnectedButton(STATE_ENABLE_WIFI_FAILED); + } + // Do not break here for disconnected event. + case STATE_CONNECTED: + if (state == STATE_DISCONNECTED) { + Log.d(TAG, "disconnected"); + // reset state + state = STATE_NONE; + updateConnectedButton(STATE_DISCONNECTED); + refreshPage(); + // clear for getting MAC Address from saved configuration + mWifiInfo = null; + } + break; + case STATE_CONNECTING: + if (state == STATE_CONNECTED) { + Log.d(TAG, "connected"); + stopTimer(); + updateConnectedButton(STATE_CONNECTED); + Toast.makeText(activity, + mContext.getString(R.string.wifi_connected_to_message, + mAccessPoint.getTitle()), + Toast.LENGTH_SHORT).show(); + + refreshPage(); + } else if (state == STATE_NOT_IN_RANGE) { + Log.d(TAG, "AP not in range"); + stopTimer(); + // reset state + state = STATE_NONE; + Toast.makeText(activity, + R.string.wifi_not_in_range_message, + Toast.LENGTH_SHORT).show(); + updateConnectedButton(STATE_NOT_IN_RANGE); + } else if (state == STATE_FAILED) { + Log.d(TAG, "failed"); + stopTimer(); + // reset state + state = STATE_NONE; + Toast.makeText(activity, + R.string.wifi_failed_connect_message, + Toast.LENGTH_SHORT).show(); + updateConnectedButton(STATE_FAILED); + } + break; + default: + Log.e(TAG, "Invalid state : " + mConnectingState); + // don't update invalid state + return; + } + + mConnectingState = state; + } + + private void updateConnectedButton(int state) { + switch (state) { + case STATE_ENABLE_WIFI: + case STATE_CONNECTING: + mButtonsPref.setButton3Text(R.string.wifi_connecting) + .setButton3Enabled(false); + break; + case STATE_CONNECTED: + // init button state and set as invisible + mButtonsPref.setButton3Text(R.string.wifi_connect) + .setButton3Icon(R.drawable.ic_settings_wireless) + .setButton3Enabled(true) + .setButton3Visible(false); + break; + case STATE_DISCONNECTED: + case STATE_NOT_IN_RANGE: + case STATE_FAILED: + case STATE_ENABLE_WIFI_FAILED: + if (isPasspointConfigurationR1Expired()) { + // Hide Connect button. + mButtonsPref.setButton3Visible(false); + } else { + mButtonsPref.setButton3Text(R.string.wifi_connect) + .setButton3Icon(R.drawable.ic_settings_wireless) + .setButton3Enabled(true) + .setButton3Visible(true); + } + break; + default: + Log.e(TAG, "Invalid connect button state : " + state); + break; + } + } + + private void startTimer() { + if (sTimer != null) { + stopTimer(); + } + + sTimer = new CountDownTimer(TIMEOUT, TIMEOUT + 1) { + @Override + public void onTick(long millisUntilFinished) { + // Do nothing + } + @Override + public void onFinish() { + if (mFragment == null || mFragment.getActivity() == null) { + Log.d(TAG, "Ignore timeout since activity not exist!"); + return; + } + Log.e(TAG, "Timeout for state:" + mConnectingState); + if (mConnectingState == STATE_ENABLE_WIFI) { + updateConnectingState(STATE_ENABLE_WIFI_FAILED); + } else if (mConnectingState == STATE_CONNECTING) { + updateAccessPointFromScannedList(); + if (mIsOutOfRange) { + updateConnectingState(STATE_NOT_IN_RANGE); + } else { + updateConnectingState(STATE_FAILED); + } + } + } + }; + sTimer.start(); + } + + private void stopTimer() { + if (sTimer == null) return; + + sTimer.cancel(); + sTimer = null; + } + + private void refreshMacTitle() { + if (mWifiConfig == null) { + return; + } + + // For saved Passpoint network, framework doesn't have the field to keep the MAC choice + // persistently, so Passpoint network will always use the default value so far, which is + // randomized MAC address, so don't need to modify title. + if (mAccessPoint.isPasspoint() || mAccessPoint.isPasspointConfig()) { + return; + } + + mMacAddressPref.setTitle( + (mWifiConfig.macRandomizationSetting + == WifiConfiguration.RANDOMIZATION_PERSISTENT) + ? R.string.wifi_advanced_randomized_mac_address_title + : R.string.wifi_advanced_device_mac_address_title); + + } +} diff --git a/src/com/android/settings/wifi/details2/WifiMeteredPreferenceController2.java b/src/com/android/settings/wifi/details2/WifiMeteredPreferenceController2.java new file mode 100644 index 00000000000..99967dcf966 --- /dev/null +++ b/src/com/android/settings/wifi/details2/WifiMeteredPreferenceController2.java @@ -0,0 +1,108 @@ +/* + * 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.details2; + +import android.app.backup.BackupManager; +import android.content.Context; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.DropDownPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.core.BasePreferenceController; +import com.android.settings.wifi.WifiDialog; +import com.android.settingslib.core.AbstractPreferenceController; + +/** + * {@link AbstractPreferenceController} that controls whether the wifi network is metered or not + */ +public class WifiMeteredPreferenceController2 extends BasePreferenceController implements + Preference.OnPreferenceChangeListener, WifiDialog.WifiDialogListener { + + private static final String KEY_WIFI_METERED = "metered"; + private WifiConfiguration mWifiConfiguration; + private WifiManager mWifiManager; + private Preference mPreference; + + public WifiMeteredPreferenceController2(Context context, WifiConfiguration wifiConfiguration) { + super(context, KEY_WIFI_METERED); + mWifiConfiguration = wifiConfiguration; + mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + } + + @Override + public void updateState(Preference preference) { + final DropDownPreference dropDownPreference = (DropDownPreference) preference; + final int meteredOverride = getMeteredOverride(); + dropDownPreference.setValue(Integer.toString(meteredOverride)); + updateSummary(dropDownPreference, meteredOverride); + } + + @Override + public int getAvailabilityStatus() { + return AVAILABLE; + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (mWifiConfiguration != null) { + mWifiConfiguration.meteredOverride = Integer.parseInt((String) newValue); + } + mWifiManager.updateNetwork(mWifiConfiguration); + // Stage the backup of the SettingsProvider package which backs this up + BackupManager.dataChanged("com.android.providers.settings"); + updateSummary((DropDownPreference) preference, getMeteredOverride()); + return true; + } + + @VisibleForTesting + int getMeteredOverride() { + if (mWifiConfiguration != null) { + // Wrap the meteredOverride since robolectric cannot recognize it + return mWifiConfiguration.meteredOverride; + } + return WifiConfiguration.METERED_OVERRIDE_NONE; + } + + private void updateSummary(DropDownPreference preference, int meteredOverride) { + preference.setSummary(preference.getEntries()[meteredOverride]); + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public void onSubmit(WifiDialog dialog) { + if (dialog.getController() != null) { + final WifiConfiguration newConfig = dialog.getController().getConfig(); + if (newConfig == null || mWifiConfiguration == null) { + return; + } + + if (newConfig.meteredOverride != mWifiConfiguration.meteredOverride) { + mWifiConfiguration = newConfig; + onPreferenceChange(mPreference, String.valueOf(newConfig.meteredOverride)); + } + } + } +} diff --git a/src/com/android/settings/wifi/details2/WifiNetworkDetailsFragment2.java b/src/com/android/settings/wifi/details2/WifiNetworkDetailsFragment2.java new file mode 100644 index 00000000000..5eb4b287268 --- /dev/null +++ b/src/com/android/settings/wifi/details2/WifiNetworkDetailsFragment2.java @@ -0,0 +1,167 @@ +/* + * 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.details2; + +import static com.android.settings.wifi.WifiSettings.WIFI_DIALOG_ID; + +import android.app.Dialog; +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.net.ConnectivityManager; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; + +import com.android.settings.R; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.wifi.WifiConfigUiBase; +import com.android.settings.wifi.WifiDialog; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedLockUtilsInternal; +import com.android.settingslib.core.AbstractPreferenceController; +import com.android.settingslib.wifi.AccessPoint; + +import java.util.ArrayList; +import java.util.List; + +/** + * Detail page for the currently connected wifi network. + * + *

The AccessPoint should be saved to the intent Extras when launching this class via + * {@link AccessPoint#saveWifiState(Bundle)} in order to properly render this page. + */ +public class WifiNetworkDetailsFragment2 extends DashboardFragment implements + WifiDialog.WifiDialogListener { + + private static final String TAG = "WifiNetworkDetailsFrg2"; + + private AccessPoint mAccessPoint; + private WifiDetailPreferenceController2 mWifiDetailPreferenceController2; + private List mWifiDialogListeners = new ArrayList<>(); + + @Override + public void onAttach(Context context) { + mAccessPoint = new AccessPoint(context, getArguments()); + super.onAttach(context); + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.WIFI_NETWORK_DETAILS; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.wifi_network_details_fragment; + } + + @Override + public int getDialogMetricsCategory(int dialogId) { + if (dialogId == WIFI_DIALOG_ID) { + return SettingsEnums.DIALOG_WIFI_AP_EDIT; + } + return 0; + } + + @Override + public Dialog onCreateDialog(int dialogId) { + if (getActivity() == null || mWifiDetailPreferenceController2 == null + || mAccessPoint == null) { + return null; + } + return WifiDialog.createModal(getActivity(), this, mAccessPoint, + WifiConfigUiBase.MODE_MODIFY); + } + + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + MenuItem item = menu.add(0, Menu.FIRST, 0, R.string.wifi_modify); + item.setIcon(com.android.internal.R.drawable.ic_mode_edit); + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS); + super.onCreateOptionsMenu(menu, inflater); + } + + @Override + public boolean onOptionsItemSelected(MenuItem menuItem) { + switch (menuItem.getItemId()) { + case Menu.FIRST: + if (!mWifiDetailPreferenceController2.canModifyNetwork()) { + RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getContext(), + RestrictedLockUtilsInternal.getDeviceOwner(getContext())); + } else { + showDialog(WIFI_DIALOG_ID); + } + return true; + default: + return super.onOptionsItemSelected(menuItem); + } + } + + @Override + protected List createPreferenceControllers(Context context) { + final List controllers = new ArrayList<>(); + final ConnectivityManager cm = context.getSystemService(ConnectivityManager.class); + + mWifiDetailPreferenceController2 = WifiDetailPreferenceController2.newInstance( + mAccessPoint, + cm, + context, + this, + new Handler(Looper.getMainLooper()), // UI thread. + getSettingsLifecycle(), + context.getSystemService(WifiManager.class), + mMetricsFeatureProvider); + + controllers.add(mWifiDetailPreferenceController2); + controllers.add(new AddDevicePreferenceController2(context).init(mAccessPoint)); + + final WifiMeteredPreferenceController2 meteredPreferenceController2 = + new WifiMeteredPreferenceController2(context, mAccessPoint.getConfig()); + controllers.add(meteredPreferenceController2); + + final WifiPrivacyPreferenceController2 privacyController2 = + new WifiPrivacyPreferenceController2(context); + privacyController2.setWifiConfiguration(mAccessPoint.getConfig()); + privacyController2.setIsEphemeral(mAccessPoint.isEphemeral()); + privacyController2.setIsPasspoint( + mAccessPoint.isPasspoint() || mAccessPoint.isPasspointConfig()); + controllers.add(privacyController2); + + // Sets callback listener for wifi dialog. + mWifiDialogListeners.add(mWifiDetailPreferenceController2); + mWifiDialogListeners.add(privacyController2); + mWifiDialogListeners.add(meteredPreferenceController2); + + return controllers; + } + + @Override + public void onSubmit(WifiDialog dialog) { + for (WifiDialog.WifiDialogListener listener : mWifiDialogListeners) { + listener.onSubmit(dialog); + } + } +} diff --git a/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController2.java b/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController2.java new file mode 100644 index 00000000000..d85b6079933 --- /dev/null +++ b/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController2.java @@ -0,0 +1,162 @@ +/* + * 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.details2; + +import android.content.Context; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.DropDownPreference; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.BasePreferenceController; +import com.android.settings.wifi.WifiDialog; +import com.android.settingslib.core.AbstractPreferenceController; + +/** + * {@link AbstractPreferenceController} that controls whether the wifi network is mac randomized + * or not + */ +public class WifiPrivacyPreferenceController2 extends BasePreferenceController implements + Preference.OnPreferenceChangeListener, WifiDialog.WifiDialogListener { + + private static final String KEY_WIFI_PRIVACY = "privacy"; + private WifiConfiguration mWifiConfiguration; + private WifiManager mWifiManager; + private boolean mIsEphemeral = false; + private boolean mIsPasspoint = false; + private Preference mPreference; + + public WifiPrivacyPreferenceController2(Context context) { + super(context, KEY_WIFI_PRIVACY); + mWifiConfiguration = null; + mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE); + } + + public void setWifiConfiguration(WifiConfiguration wifiConfiguration) { + mWifiConfiguration = wifiConfiguration; + } + + public void setIsEphemeral(boolean isEphemeral) { + mIsEphemeral = isEphemeral; + } + + public void setIsPasspoint(boolean isPasspoint) { + mIsPasspoint = isPasspoint; + } + + @Override + public int getAvailabilityStatus() { + return mWifiManager.isConnectedMacRandomizationSupported() + ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + super.displayPreference(screen); + mPreference = screen.findPreference(getPreferenceKey()); + } + + @Override + public void updateState(Preference preference) { + final DropDownPreference dropDownPreference = (DropDownPreference) preference; + final int randomizationLevel = getRandomizationValue(); + dropDownPreference.setValue(Integer.toString(randomizationLevel)); + updateSummary(dropDownPreference, randomizationLevel); + + // Makes preference not selectable, when this is a ephemeral network. + if (mIsEphemeral || mIsPasspoint) { + preference.setSelectable(false); + dropDownPreference.setSummary(R.string.wifi_privacy_settings_ephemeral_summary); + } + } + + @Override + public boolean onPreferenceChange(Preference preference, Object newValue) { + if (mWifiConfiguration != null) { + mWifiConfiguration.macRandomizationSetting = Integer.parseInt((String) newValue); + mWifiManager.updateNetwork(mWifiConfiguration); + + // To activate changing, we need to reconnect network. WiFi will auto connect to + // current network after disconnect(). Only needed when this is connected network. + final WifiInfo wifiInfo = mWifiManager.getConnectionInfo(); + if (wifiInfo != null && wifiInfo.getNetworkId() == mWifiConfiguration.networkId) { + mWifiManager.disconnect(); + } + } + updateSummary((DropDownPreference) preference, Integer.parseInt((String) newValue)); + return true; + } + + @VisibleForTesting + int getRandomizationValue() { + if (mWifiConfiguration != null) { + return mWifiConfiguration.macRandomizationSetting; + } + return WifiConfiguration.RANDOMIZATION_PERSISTENT; + } + + private static final int PREF_RANDOMIZATION_PERSISTENT = 0; + private static final int PREF_RANDOMIZATION_NONE = 1; + + /** + * Returns preference index value. + * + * @param macRandomized is mac randomized value + * @return index value of preference + */ + public static int translateMacRandomizedValueToPrefValue(int macRandomized) { + return (macRandomized == WifiConfiguration.RANDOMIZATION_PERSISTENT) + ? PREF_RANDOMIZATION_PERSISTENT : PREF_RANDOMIZATION_NONE; + } + + /** + * Returns mac randomized value. + * + * @param prefMacRandomized is preference index value + * @return mac randomized value + */ + public static int translatePrefValueToMacRandomizedValue(int prefMacRandomized) { + return (prefMacRandomized == PREF_RANDOMIZATION_PERSISTENT) + ? WifiConfiguration.RANDOMIZATION_PERSISTENT : WifiConfiguration.RANDOMIZATION_NONE; + } + + private void updateSummary(DropDownPreference preference, int macRandomized) { + // Translates value here to set RANDOMIZATION_PERSISTENT as first item in UI for better UX. + final int prefMacRandomized = translateMacRandomizedValueToPrefValue(macRandomized); + preference.setSummary(preference.getEntries()[prefMacRandomized]); + } + + @Override + public void onSubmit(WifiDialog dialog) { + if (dialog.getController() != null) { + final WifiConfiguration newConfig = dialog.getController().getConfig(); + if (newConfig == null || mWifiConfiguration == null) { + return; + } + + if (newConfig.macRandomizationSetting != mWifiConfiguration.macRandomizationSetting) { + mWifiConfiguration = newConfig; + onPreferenceChange(mPreference, String.valueOf(newConfig.macRandomizationSetting)); + } + } + } +} diff --git a/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsPreferenceController2.java b/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsPreferenceController2.java new file mode 100644 index 00000000000..3b8eb27ada3 --- /dev/null +++ b/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsPreferenceController2.java @@ -0,0 +1,107 @@ +/* + * 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.savedaccesspoints2; + +import android.content.Context; +import android.net.wifi.WifiManager; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceGroup; +import androidx.preference.PreferenceScreen; + +import com.android.settings.core.BasePreferenceController; +import com.android.settingslib.wifi.AccessPoint; +import com.android.settingslib.wifi.AccessPointPreference; +import com.android.settingslib.wifi.AccessPointPreference.UserBadgeCache; +import com.android.settingslib.wifi.WifiSavedConfigUtils; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * Controller that manages a PreferenceGroup, which contains a list of saved access points. + */ +public class SavedAccessPointsPreferenceController2 extends BasePreferenceController implements + Preference.OnPreferenceClickListener { + + protected final WifiManager mWifiManager; + private final UserBadgeCache mUserBadgeCache; + private PreferenceGroup mPreferenceGroup; + private SavedAccessPointsWifiSettings2 mHost; + @VisibleForTesting + List mAccessPoints; + + public SavedAccessPointsPreferenceController2(Context context, String preferenceKey) { + super(context, preferenceKey); + mUserBadgeCache = new AccessPointPreference.UserBadgeCache(context.getPackageManager()); + mWifiManager = context.getSystemService(WifiManager.class); + } + + /** + * Set {@link SavedAccessPointsWifiSettings2} for click callback action. + */ + public SavedAccessPointsPreferenceController2 setHost(SavedAccessPointsWifiSettings2 host) { + mHost = host; + return this; + } + + @Override + public int getAvailabilityStatus() { + return mAccessPoints.size() > 0 ? AVAILABLE : CONDITIONALLY_UNAVAILABLE; + } + + @Override + public void displayPreference(PreferenceScreen screen) { + mPreferenceGroup = screen.findPreference(getPreferenceKey()); + refreshSavedAccessPoints(); + updatePreference(); + super.displayPreference(screen); + } + + @Override + public boolean onPreferenceClick(Preference preference) { + if (mHost != null) { + final Preference preferenceInGroup = + mPreferenceGroup.findPreference(preference.getKey()); + mHost.showWifiPage((AccessPointPreference) preferenceInGroup); + } + return false; + } + + protected void refreshSavedAccessPoints() { + mAccessPoints = WifiSavedConfigUtils.getAllConfigs(mContext, mWifiManager).stream() + .filter(accessPoint -> !accessPoint.isPasspointConfig()) + .sorted(SavedNetworkComparator2.INSTANCE) + .collect(Collectors.toList()); + } + + private void updatePreference() { + mPreferenceGroup.removeAll(); + for (AccessPoint accessPoint : mAccessPoints) { + final String key = accessPoint.getKey(); + + final AccessPointPreference preference = new AccessPointPreference(accessPoint, + mContext, mUserBadgeCache, true /* forSavedNetworks */); + preference.setKey(key); + preference.setIcon(null); + preference.setOnPreferenceClickListener(this); + + mPreferenceGroup.addPreference(preference); + } + } +} diff --git a/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsWifiSettings2.java b/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsWifiSettings2.java new file mode 100644 index 00000000000..a1b7733043b --- /dev/null +++ b/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsWifiSettings2.java @@ -0,0 +1,136 @@ +/* + * 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.savedaccesspoints2; + +import android.annotation.Nullable; +import android.app.settings.SettingsEnums; +import android.content.Context; +import android.os.Bundle; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.PreferenceScreen; + +import com.android.settings.R; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.dashboard.DashboardFragment; +import com.android.settings.wifi.WifiSettings; +import com.android.settings.wifi.details2.WifiNetworkDetailsFragment2; +import com.android.settingslib.wifi.AccessPoint; +import com.android.settingslib.wifi.AccessPointPreference; + +/** + * UI to manage saved networks/access points. + */ +public class SavedAccessPointsWifiSettings2 extends DashboardFragment { + + private static final String TAG = "SavedAccessPoints2"; + + @VisibleForTesting + Bundle mAccessPointSavedState; + private AccessPoint mSelectedAccessPoint; + + // Instance state key + private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state"; + + @Override + public int getMetricsCategory() { + return SettingsEnums.WIFI_SAVED_ACCESS_POINTS; + } + + @Override + protected int getPreferenceScreenResId() { + return R.xml.wifi_display_saved_access_points2; + } + + @Override + protected String getLogTag() { + return TAG; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + use(SavedAccessPointsPreferenceController2.class) + .setHost(this); + use(SubscribedAccessPointsPreferenceController2.class) + .setHost(this); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if (savedInstanceState != null) { + if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) { + mAccessPointSavedState = + savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE); + } else { + mAccessPointSavedState = null; + } + } + } + + @Override + public void onStart() { + super.onStart(); + if (mAccessPointSavedState != null) { + final PreferenceScreen screen = getPreferenceScreen(); + use(SavedAccessPointsPreferenceController2.class).displayPreference(screen); + use(SubscribedAccessPointsPreferenceController2.class).displayPreference(screen); + } + } + + /** + * Shows {@link WifiNetworkDetailsFragment2} for assigned {@link AccessPointPreference}. + */ + public void showWifiPage(@Nullable AccessPointPreference accessPoint) { + removeDialog(WifiSettings.WIFI_DIALOG_ID); + + if (accessPoint != null) { + // Save the access point and edit mode + mSelectedAccessPoint = accessPoint.getAccessPoint(); + } else { + // No access point is selected. Clear saved state. + mSelectedAccessPoint = null; + mAccessPointSavedState = null; + } + + if (mSelectedAccessPoint == null) { + mSelectedAccessPoint = new AccessPoint(getActivity(), mAccessPointSavedState); + } + final Bundle savedState = new Bundle(); + mSelectedAccessPoint.saveWifiState(savedState); + + new SubSettingLauncher(getContext()) + .setTitleText(mSelectedAccessPoint.getTitle()) + .setDestination(WifiNetworkDetailsFragment2.class.getName()) + .setArguments(savedState) + .setSourceMetricsCategory(getMetricsCategory()) + .launch(); + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + // If the dialog is showing (indicated by the existence of mSelectedAccessPoint), then we + // save its state. + if (mSelectedAccessPoint != null) { + mAccessPointSavedState = new Bundle(); + mSelectedAccessPoint.saveWifiState(mAccessPointSavedState); + outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState); + } + } +} diff --git a/src/com/android/settings/wifi/savedaccesspoints2/SavedNetworkComparator2.java b/src/com/android/settings/wifi/savedaccesspoints2/SavedNetworkComparator2.java new file mode 100644 index 00000000000..8388531d794 --- /dev/null +++ b/src/com/android/settings/wifi/savedaccesspoints2/SavedNetworkComparator2.java @@ -0,0 +1,43 @@ +/* + * 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.savedaccesspoints2; + +import android.icu.text.Collator; + +import com.android.settingslib.wifi.AccessPoint; + +import java.util.Comparator; + +/** + * For {@link AccessPoint} sorting before desplaying. + */ +public final class SavedNetworkComparator2 { + public static final Comparator INSTANCE = + new Comparator() { + final Collator mCollator = Collator.getInstance(); + + @Override + public int compare(AccessPoint ap1, AccessPoint ap2) { + return mCollator.compare( + nullToEmpty(ap1.getTitle()), nullToEmpty(ap2.getTitle())); + } + + private String nullToEmpty(String string) { + return (string == null) ? "" : string; + } + }; +} diff --git a/src/com/android/settings/wifi/savedaccesspoints2/SubscribedAccessPointsPreferenceController2.java b/src/com/android/settings/wifi/savedaccesspoints2/SubscribedAccessPointsPreferenceController2.java new file mode 100644 index 00000000000..00ae2218686 --- /dev/null +++ b/src/com/android/settings/wifi/savedaccesspoints2/SubscribedAccessPointsPreferenceController2.java @@ -0,0 +1,42 @@ +/* + * 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.savedaccesspoints2; + +import android.content.Context; + +import com.android.settingslib.wifi.WifiSavedConfigUtils; + +import java.util.stream.Collectors; + +/** + * Controller that manages a PreferenceGroup, which contains a list of subscribed access points. + */ +public class SubscribedAccessPointsPreferenceController2 extends + SavedAccessPointsPreferenceController2 { + + public SubscribedAccessPointsPreferenceController2(Context context, String preferenceKey) { + super(context, preferenceKey); + } + + @Override + protected void refreshSavedAccessPoints() { + mAccessPoints = WifiSavedConfigUtils.getAllConfigs(mContext, mWifiManager).stream() + .filter(accessPoint -> accessPoint.isPasspointConfig()) + .sorted(SavedNetworkComparator2.INSTANCE) + .collect(Collectors.toList()); + } +} diff --git a/src/com/android/settings/wifi/slice/WifiSlice.java b/src/com/android/settings/wifi/slice/WifiSlice.java index e5c8de59f8c..97ac9c55a56 100644 --- a/src/com/android/settings/wifi/slice/WifiSlice.java +++ b/src/com/android/settings/wifi/slice/WifiSlice.java @@ -38,6 +38,7 @@ import android.net.Uri; import android.net.wifi.WifiManager; import android.os.Bundle; import android.text.TextUtils; +import android.util.FeatureFlagUtils; import androidx.annotation.VisibleForTesting; import androidx.core.graphics.drawable.IconCompat; @@ -54,8 +55,10 @@ import com.android.settings.slices.SliceBackgroundWorker; import com.android.settings.slices.SliceBuilderUtils; import com.android.settings.wifi.WifiDialogActivity; import com.android.settings.wifi.WifiSettings; +import com.android.settings.wifi.WifiSettings2; import com.android.settings.wifi.WifiUtils; import com.android.settings.wifi.details.WifiNetworkDetailsFragment; +import com.android.settings.wifi.details2.WifiNetworkDetailsFragment2; import com.android.settingslib.wifi.AccessPoint; import java.util.Arrays; @@ -247,13 +250,24 @@ public class WifiSlice implements CustomSliceable { accessPoint.saveWifiState(extras); if (accessPoint.isActive()) { - final Intent intent = new SubSettingLauncher(mContext) - .setTitleRes(R.string.pref_title_network_details) - .setDestination(WifiNetworkDetailsFragment.class.getName()) - .setArguments(extras) - .setSourceMetricsCategory(SettingsEnums.WIFI) - .toIntent(); - return getActivityAction(requestCode, intent, icon, title); + Intent intent; + if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_WIFITRACKER2)) { + intent = new SubSettingLauncher(mContext) + .setTitleRes(R.string.pref_title_network_details) + .setDestination(WifiNetworkDetailsFragment2.class.getName()) + .setArguments(extras) + .setSourceMetricsCategory(SettingsEnums.WIFI) + .toIntent(); + return getActivityAction(requestCode, intent, icon, title); + } else { + intent = new SubSettingLauncher(mContext) + .setTitleRes(R.string.pref_title_network_details) + .setDestination(WifiNetworkDetailsFragment.class.getName()) + .setArguments(extras) + .setSourceMetricsCategory(SettingsEnums.WIFI) + .toIntent(); + return getActivityAction(requestCode, intent, icon, title); + } } else if (WifiUtils.getConnectingType(accessPoint) != WifiUtils.CONNECT_TYPE_OTHERS) { final Intent intent = new Intent(mContext, ConnectToWifiHandler.class) .putExtra(WifiDialogActivity.KEY_ACCESS_POINT_STATE, extras); @@ -317,11 +331,21 @@ public class WifiSlice implements CustomSliceable { public Intent getIntent() { final String screenTitle = mContext.getText(R.string.wifi_settings).toString(); final Uri contentUri = new Uri.Builder().appendPath(KEY_WIFI).build(); - final Intent intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext, + + Intent intent; + if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_WIFITRACKER2)) { + intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext, + WifiSettings2.class.getName(), KEY_WIFI, screenTitle, + SettingsEnums.DIALOG_WIFI_AP_EDIT) + .setClassName(mContext.getPackageName(), SubSettings.class.getName()) + .setData(contentUri); + } else { + intent = SliceBuilderUtils.buildSearchResultPageIntent(mContext, WifiSettings.class.getName(), KEY_WIFI, screenTitle, SettingsEnums.DIALOG_WIFI_AP_EDIT) .setClassName(mContext.getPackageName(), SubSettings.class.getName()) .setData(contentUri); + } return intent; } diff --git a/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java b/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java new file mode 100644 index 00000000000..8d15224229d --- /dev/null +++ b/tests/robotests/src/com/android/settings/wifi/details2/WifiDetailPreferenceController2Test.java @@ -0,0 +1,1905 @@ +/* + * 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.details2; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.nullable; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.inOrder; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.ColorDrawable; +import android.graphics.drawable.Drawable; +import android.net.ConnectivityManager; +import android.net.ConnectivityManager.NetworkCallback; +import android.net.IpPrefix; +import android.net.LinkAddress; +import android.net.LinkProperties; +import android.net.MacAddress; +import android.net.Network; +import android.net.NetworkCapabilities; +import android.net.NetworkInfo; +import android.net.NetworkRequest; +import android.net.RouteInfo; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiInfo; +import android.net.wifi.WifiManager; +import android.os.Handler; +import android.provider.Settings; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.ImageView; + +import androidx.fragment.app.FragmentActivity; +import androidx.lifecycle.LifecycleOwner; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; + +import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.R; +import com.android.settings.Utils; +import com.android.settings.testutils.shadow.ShadowDevicePolicyManager; +import com.android.settings.testutils.shadow.ShadowEntityHeaderController; +import com.android.settings.widget.EntityHeaderController; +import com.android.settingslib.core.instrumentation.MetricsFeatureProvider; +import com.android.settingslib.core.lifecycle.Lifecycle; +import com.android.settingslib.widget.ActionButtonsPreference; +import com.android.settingslib.widget.LayoutPreference; +import com.android.settingslib.wifi.AccessPoint; +import com.android.settingslib.wifi.WifiTracker; +import com.android.settingslib.wifi.WifiTrackerFactory; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InOrder; +import org.mockito.Matchers; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowToast; + +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.stream.Collectors; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowDevicePolicyManager.class, ShadowEntityHeaderController.class}) +public class WifiDetailPreferenceController2Test { + + private static final int LEVEL = 1; + private static final int RSSI = -55; + private static final int TX_LINK_SPEED = 123; + private static final int RX_LINK_SPEED = 54; + private static final String SSID = "ssid"; + private static final String MAC_ADDRESS = "01:23:45:67:89:ab"; + private static final String RANDOMIZED_MAC_ADDRESS = "RANDOMIZED_MAC_ADDRESS"; + private static final String FACTORY_MAC_ADDRESS = "FACTORY_MAC_ADDRESS"; + private static final String SECURITY = "None"; + private static final String FQDN = "fqdn"; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private PreferenceScreen mMockScreen; + + @Mock + private AccessPoint mMockAccessPoint; + @Mock + private FragmentActivity mMockActivity; + @Mock + private ConnectivityManager mMockConnectivityManager; + @Mock + private Network mMockNetwork; + @Mock + private NetworkInfo mMockNetworkInfo; + @Mock + private WifiConfiguration mMockWifiConfig; + @Mock + private WifiInfo mMockWifiInfo; + @Mock + private WifiNetworkDetailsFragment2 mMockFragment; + @Mock + private WifiManager mMockWifiManager; + @Mock + private WifiTracker mMockWifiTracker; + @Mock + private MetricsFeatureProvider mMockMetricsFeatureProvider; + @Mock + private WifiDetailPreferenceController2.IconInjector mMockIconInjector; + @Mock + private MacAddress mMockMacAddress; + + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private EntityHeaderController mMockHeaderController; + @Mock(answer = Answers.RETURNS_DEEP_STUBS) + private LayoutPreference mMockHeaderLayoutPreference; + @Mock + private ImageView mMockHeaderIcon; + + @Mock + private ActionButtonsPreference mMockButtonsPref; + @Mock + private Preference mMockSignalStrengthPref; + @Mock + private Preference mMockTxLinkSpeedPref; + @Mock + private Preference mMockRxLinkSpeedPref; + @Mock + private Preference mMockFrequencyPref; + @Mock + private Preference mMockSecurityPref; + @Mock + private Preference mMockSsidPref; + @Mock + private Preference mMockMacAddressPref; + @Mock + private Preference mMockIpAddressPref; + @Mock + private Preference mMockGatewayPref; + @Mock + private Preference mMockSubnetPref; + @Mock + private Preference mMockDnsPref; + @Mock + private PreferenceCategory mMockIpv6Category; + @Mock + private Preference mMockIpv6AddressesPref; + @Mock + private PackageManager mMockPackageManager; + + @Captor + private ArgumentCaptor mCallbackCaptor; + @Captor + private ArgumentCaptor mForgetClickListener; + + private Context mContext; + private Lifecycle mLifecycle; + private LifecycleOwner mLifecycleOwner; + private LinkProperties mLinkProperties; + private WifiDetailPreferenceController2 mController; + + // This class exists so that these values can be made static final. They can't be static final + // members of the test class, because any attempt to call IpPrefix or RouteInfo constructors + // during static initialization of the test class results in NoSuchMethorError being thrown + // when the test is run. + private static class Constants { + static final int IPV4_PREFIXLEN = 25; + static final LinkAddress IPV4_ADDR; + static final Inet4Address IPV4_GATEWAY; + static final RouteInfo IPV4_DEFAULT; + static final RouteInfo IPV4_SUBNET; + static final LinkAddress IPV6_LINKLOCAL; + static final LinkAddress IPV6_GLOBAL1; + static final LinkAddress IPV6_GLOBAL2; + static final InetAddress IPV4_DNS1; + static final InetAddress IPV4_DNS2; + static final InetAddress IPV6_DNS; + + private static LinkAddress ipv6LinkAddress(String addr) throws UnknownHostException { + return new LinkAddress(InetAddress.getByName(addr), 64); + } + + private static LinkAddress ipv4LinkAddress(String addr, int prefixlen) + throws UnknownHostException { + return new LinkAddress(InetAddress.getByName(addr), prefixlen); + } + + static { + try { + // We create our test constants in these roundabout ways because the robolectric + // shadows don't contain NetworkUtils.parseNumericAddress and other utility methods, + // so the easy ways to do things fail with NoSuchMethodError. + IPV4_ADDR = ipv4LinkAddress("192.0.2.2", IPV4_PREFIXLEN); + IPV4_GATEWAY = (Inet4Address) InetAddress.getByName("192.0.2.127"); + + final Inet4Address any4 = (Inet4Address) InetAddress.getByName("0.0.0.0"); + IpPrefix subnet = new IpPrefix(IPV4_ADDR.getAddress(), IPV4_PREFIXLEN); + IPV4_SUBNET = new RouteInfo(subnet, any4); + IPV4_DEFAULT = new RouteInfo(new IpPrefix(any4, 0), IPV4_GATEWAY); + + IPV6_LINKLOCAL = ipv6LinkAddress("fe80::211:25ff:fef8:7cb2%1"); + IPV6_GLOBAL1 = ipv6LinkAddress("2001:db8:1::211:25ff:fef8:7cb2"); + IPV6_GLOBAL2 = ipv6LinkAddress("2001:db8:1::3dfe:8902:f98f:739d"); + + IPV4_DNS1 = InetAddress.getByName("8.8.8.8"); + IPV4_DNS2 = InetAddress.getByName("8.8.4.4"); + IPV6_DNS = InetAddress.getByName("2001:4860:4860::64"); + } catch (UnknownHostException e) { + throw new RuntimeException("Invalid hardcoded IP addresss: " + e); + } + } + } + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + mContext = spy(RuntimeEnvironment.application); + mLifecycleOwner = () -> mLifecycle; + mLifecycle = new Lifecycle(mLifecycleOwner); + + when(mContext.getPackageManager()).thenReturn(mMockPackageManager); + when(mMockAccessPoint.getConfig()).thenReturn(mMockWifiConfig); + when(mMockAccessPoint.getLevel()).thenReturn(LEVEL); + when(mMockAccessPoint.getSecurityString(false)).thenReturn(SECURITY); + when(mMockAccessPoint.getSsidStr()).thenReturn(SSID); + when(mMockConnectivityManager.getNetworkInfo(any(Network.class))) + .thenReturn(mMockNetworkInfo); + doNothing().when(mMockConnectivityManager).registerNetworkCallback( + nullable(NetworkRequest.class), mCallbackCaptor.capture(), nullable(Handler.class)); + mMockButtonsPref = createMock(); + when(mMockButtonsPref.setButton1OnClickListener(mForgetClickListener.capture())) + .thenReturn(mMockButtonsPref); + + when(mMockWifiInfo.getTxLinkSpeedMbps()).thenReturn(TX_LINK_SPEED); + when(mMockWifiInfo.getRxLinkSpeedMbps()).thenReturn(RX_LINK_SPEED); + when(mMockWifiInfo.getRssi()).thenReturn(RSSI); + when(mMockWifiInfo.getMacAddress()).thenReturn(MAC_ADDRESS); + when(mMockWifiManager.getConnectionInfo()).thenReturn(mMockWifiInfo); + + when(mMockWifiManager.getCurrentNetwork()).thenReturn(mMockNetwork); + mLinkProperties = new LinkProperties(); + when(mMockConnectivityManager.getLinkProperties(mMockNetwork)).thenReturn(mLinkProperties); + + when(mMockFragment.getActivity()).thenReturn(mMockActivity); + + ShadowEntityHeaderController.setUseMock(mMockHeaderController); + // builder pattern + when(mMockHeaderController.setRecyclerView(mMockFragment.getListView(), mLifecycle)) + .thenReturn(mMockHeaderController); + when(mMockHeaderController.setSummary(anyString())).thenReturn(mMockHeaderController); + when(mMockIconInjector.getIcon(anyInt())).thenReturn(new ColorDrawable()); + + setupMockedPreferenceScreen(); + } + + private void setUpForConnectedNetwork() { + when(mMockAccessPoint.isActive()).thenReturn(true); + ArrayList list = new ArrayList<>(); + list.add(mMockAccessPoint); + when(mMockWifiTracker.getAccessPoints()).thenReturn(list); + WifiTrackerFactory.setTestingWifiTracker(mMockWifiTracker); + when(mMockAccessPoint.matches(any(AccessPoint.class))).thenReturn(true); + when(mMockAccessPoint.isReachable()).thenReturn(true); + + mController = newWifiDetailPreferenceController2(); + } + + private void setUpForDisconnectedNetwork() { + when(mMockAccessPoint.isActive()).thenReturn(false); + ArrayList list = new ArrayList<>(); + list.add(mMockAccessPoint); + when(mMockWifiTracker.getAccessPoints()).thenReturn(list); + WifiTrackerFactory.setTestingWifiTracker(mMockWifiTracker); + when(mMockAccessPoint.matches(any(AccessPoint.class))).thenReturn(true); + when(mMockAccessPoint.isReachable()).thenReturn(true); + + mController = newWifiDetailPreferenceController2(); + } + + private void setUpForNotInRangeNetwork() { + when(mMockAccessPoint.isActive()).thenReturn(false); + ArrayList list = new ArrayList<>(); + list.add(mMockAccessPoint); + when(mMockWifiTracker.getAccessPoints()).thenReturn(list); + WifiTrackerFactory.setTestingWifiTracker(mMockWifiTracker); + when(mMockAccessPoint.matches(any(AccessPoint.class))).thenReturn(false); + when(mMockAccessPoint.isReachable()).thenReturn(false); + + mController = newWifiDetailPreferenceController2(); + } + + private WifiDetailPreferenceController2 newWifiDetailPreferenceController2() { + return new WifiDetailPreferenceController2( + mMockAccessPoint, + mMockConnectivityManager, + mContext, + mMockFragment, + null, // Handler + mLifecycle, + mMockWifiManager, + mMockMetricsFeatureProvider, + mMockIconInjector); + } + + private void setupMockedPreferenceScreen() { + when(mMockScreen.getPreferenceManager().getContext()).thenReturn(mContext); + + when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_HEADER)) + .thenReturn(mMockHeaderLayoutPreference); + when(mMockHeaderLayoutPreference.findViewById(R.id.entity_header_icon)) + .thenReturn(mMockHeaderIcon); + + when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_BUTTONS_PREF)) + .thenReturn(mMockButtonsPref); + when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_SIGNAL_STRENGTH_PREF)) + .thenReturn(mMockSignalStrengthPref); + when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_TX_LINK_SPEED)) + .thenReturn(mMockTxLinkSpeedPref); + when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_RX_LINK_SPEED)) + .thenReturn(mMockRxLinkSpeedPref); + when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_FREQUENCY_PREF)) + .thenReturn(mMockFrequencyPref); + when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_SECURITY_PREF)) + .thenReturn(mMockSecurityPref); + when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_SSID_PREF)) + .thenReturn(mMockSsidPref); + when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_MAC_ADDRESS_PREF)) + .thenReturn(mMockMacAddressPref); + when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_IP_ADDRESS_PREF)) + .thenReturn(mMockIpAddressPref); + when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_GATEWAY_PREF)) + .thenReturn(mMockGatewayPref); + when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_SUBNET_MASK_PREF)) + .thenReturn(mMockSubnetPref); + when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_DNS_PREF)) + .thenReturn(mMockDnsPref); + when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_IPV6_CATEGORY)) + .thenReturn(mMockIpv6Category); + when(mMockScreen.findPreference(WifiDetailPreferenceController2.KEY_IPV6_ADDRESSES_PREF)) + .thenReturn(mMockIpv6AddressesPref); + } + + private void displayAndResume() { + mController.displayPreference(mMockScreen); + mController.onResume(); + } + + @Test + public void isAvailable_shouldAlwaysReturnTrue() { + setUpForConnectedNetwork(); + mController.displayPreference(mMockScreen); + + assertThat(mController.isAvailable()).isTrue(); + } + + @Test + public void securityPreference_stringShouldBeSet() { + setUpForConnectedNetwork(); + displayAndResume(); + + verify(mMockSecurityPref).setSummary(SECURITY); + } + + @Test + public void latestWifiInfo_shouldBeFetchedInDisplayPreferenceForConnectedNetwork() { + setUpForConnectedNetwork(); + + displayAndResume(); + + verify(mMockWifiManager, times(1)).getConnectionInfo(); + } + + @Test + public void latestWifiInfo_shouldNotBeFetchedInDisplayPreferenceForDisconnectedNetwork() { + setUpForDisconnectedNetwork(); + + displayAndResume(); + + verify(mMockWifiManager, never()).getConnectionInfo(); + } + + @Test + public void latestWifiInfo_shouldNotBeFetchedInDisplayPreferenceForNotInRangeNetwork() { + setUpForNotInRangeNetwork(); + + displayAndResume(); + + verify(mMockWifiManager, never()).getConnectionInfo(); + } + + @Test + public void latestNetworkInfo_shouldBeFetchedInDisplayPreferenceForConnectedNetwork() { + setUpForConnectedNetwork(); + + displayAndResume(); + + verify(mMockConnectivityManager, times(1)).getNetworkInfo(any(Network.class)); + } + + @Test + public void latestNetworkInfo_shouldNotBeFetchedInDisplayPreferenceForDisconnectedNetwork() { + setUpForDisconnectedNetwork(); + + displayAndResume(); + + verify(mMockConnectivityManager, never()).getNetworkInfo(any(Network.class)); + } + + @Test + public void latestNetworkInfo_shouldNotBeFetchedInDisplayPreferenceForNotInRangeNetwork() { + setUpForNotInRangeNetwork(); + + displayAndResume(); + + verify(mMockConnectivityManager, never()).getNetworkInfo(any(Network.class)); + } + + @Test + public void networkCallback_shouldBeRegisteredOnResume() { + setUpForConnectedNetwork(); + displayAndResume(); + + verify(mMockConnectivityManager, times(1)).registerNetworkCallback( + nullable(NetworkRequest.class), mCallbackCaptor.capture(), nullable(Handler.class)); + } + + @Test + public void networkCallback_shouldBeUnregisteredOnPause() { + setUpForConnectedNetwork(); + displayAndResume(); + mController.onPause(); + + verify(mMockConnectivityManager, times(1)) + .unregisterNetworkCallback(mCallbackCaptor.getValue()); + } + + @Test + public void entityHeader_shouldHaveIconSetForConnectedNetwork() { + setUpForConnectedNetwork(); + Drawable expectedIcon = mMockIconInjector.getIcon(LEVEL); + + displayAndResume(); + + verify(mMockHeaderController).setIcon(expectedIcon); + } + + @Test + public void entityHeader_shouldHaveIconSetForDisconnectedNetwork() { + setUpForDisconnectedNetwork(); + Drawable expectedIcon = mMockIconInjector.getIcon(LEVEL); + + displayAndResume(); + + verify(mMockHeaderController).setIcon(expectedIcon); + } + + @Test + public void entityHeader_shouldNotHaveIconSetForNotInRangeNetwork() { + setUpForNotInRangeNetwork(); + + displayAndResume(); + + verify(mMockHeaderController, never()).setIcon(any(Drawable.class)); + } + + @Test + public void entityHeader_shouldHaveLabelSetToTitle() { + setUpForConnectedNetwork(); + String label = "title"; + when(mMockAccessPoint.getTitle()).thenReturn(label); + + displayAndResume(); + + verify(mMockHeaderController).setLabel(label); + } + + @Test + public void entityHeader_shouldHaveSummarySet() { + setUpForConnectedNetwork(); + String summary = "summary"; + when(mMockAccessPoint.getSettingsSummary(true /*convertSavedAsDisconnected*/)) + .thenReturn(summary); + + displayAndResume(); + + verify(mMockHeaderController).setSummary(summary); + } + + @Test + public void entityHeader_shouldConvertSavedAsDisconnected() { + setUpForDisconnectedNetwork(); + + displayAndResume(); + + verify(mMockAccessPoint, times(1)).getSettingsSummary(true /*convertSavedAsDisconnected*/); + } + + @Test + public void signalStrengthPref_shouldHaveIconSetForConnectedNetwork() { + setUpForConnectedNetwork(); + + displayAndResume(); + + verify(mMockSignalStrengthPref).setIcon(any(Drawable.class)); + } + + @Test + public void signalStrengthPref_shouldHaveIconSetForDisconnectedNetwork() { + setUpForDisconnectedNetwork(); + + displayAndResume(); + + verify(mMockSignalStrengthPref).setIcon(any(Drawable.class)); + } + + @Test + public void signalStrengthPref_shouldNotHaveIconSetForOutOfRangeNetwork() { + setUpForNotInRangeNetwork(); + + displayAndResume(); + + verify(mMockSignalStrengthPref, never()).setIcon(any(Drawable.class)); + } + + @Test + public void signalStrengthPref_shouldHaveDetailTextSetForConnectedNetwork() { + setUpForConnectedNetwork(); + String expectedStrength = + mContext.getResources().getStringArray(R.array.wifi_signal)[LEVEL]; + + displayAndResume(); + + verify(mMockSignalStrengthPref).setSummary(expectedStrength); + } + + @Test + public void signalStrengthPref_shouldHaveDetailTextSetForDisconnectedNetwork() { + setUpForDisconnectedNetwork(); + String expectedStrength = + mContext.getResources().getStringArray(R.array.wifi_signal)[LEVEL]; + + displayAndResume(); + + verify(mMockSignalStrengthPref).setSummary(expectedStrength); + } + + @Test + public void signalStrengthPref_shouldNotHaveDetailTextSetForNotInRangeNetwork() { + setUpForNotInRangeNetwork(); + + displayAndResume(); + + verify(mMockSignalStrengthPref, never()).setSummary(any(String.class)); + } + + @Test + public void linkSpeedPref_shouldNotShowIfNotSet() { + setUpForConnectedNetwork(); + when(mMockWifiInfo.getTxLinkSpeedMbps()).thenReturn(WifiInfo.LINK_SPEED_UNKNOWN); + + displayAndResume(); + + verify(mMockTxLinkSpeedPref).setVisible(false); + } + + @Test + public void linkSpeedPref_shouldVisibleForConnectedNetwork() { + setUpForConnectedNetwork(); + String expectedLinkSpeed = mContext.getString(R.string.tx_link_speed, TX_LINK_SPEED); + + displayAndResume(); + + verify(mMockTxLinkSpeedPref).setVisible(true); + verify(mMockTxLinkSpeedPref).setSummary(expectedLinkSpeed); + } + + @Test + public void linkSpeedPref_shouldInvisibleForDisconnectedNetwork() { + setUpForDisconnectedNetwork(); + + displayAndResume(); + + verify(mMockTxLinkSpeedPref).setVisible(false); + verify(mMockTxLinkSpeedPref, never()).setSummary(any(String.class)); + } + + @Test + public void linkSpeedPref_shouldInvisibleForNotInRangeNetwork() { + setUpForNotInRangeNetwork(); + + displayAndResume(); + + verify(mMockTxLinkSpeedPref).setVisible(false); + verify(mMockTxLinkSpeedPref, never()).setSummary(any(String.class)); + } + + @Test + public void rxLinkSpeedPref_shouldNotShowIfNotSet() { + setUpForConnectedNetwork(); + when(mMockWifiInfo.getRxLinkSpeedMbps()).thenReturn(WifiInfo.LINK_SPEED_UNKNOWN); + + displayAndResume(); + + verify(mMockRxLinkSpeedPref).setVisible(false); + } + + @Test + public void rxLinkSpeedPref_shouldVisibleForConnectedNetwork() { + setUpForConnectedNetwork(); + String expectedLinkSpeed = mContext.getString(R.string.rx_link_speed, RX_LINK_SPEED); + + displayAndResume(); + + verify(mMockRxLinkSpeedPref).setVisible(true); + verify(mMockRxLinkSpeedPref).setSummary(expectedLinkSpeed); + } + + @Test + public void rxLinkSpeedPref_shouldInvisibleForDisconnectedNetwork() { + setUpForDisconnectedNetwork(); + + displayAndResume(); + + verify(mMockRxLinkSpeedPref).setVisible(false); + verify(mMockRxLinkSpeedPref, never()).setSummary(any(String.class)); + } + + @Test + public void rxLinkSpeedPref_shouldInvisibleForNotInRangeNetwork() { + setUpForNotInRangeNetwork(); + + displayAndResume(); + + verify(mMockRxLinkSpeedPref).setVisible(false); + verify(mMockRxLinkSpeedPref, never()).setSummary(any(String.class)); + } + + @Test + public void ssidPref_shouldHaveDetailTextSetForPasspointR1() { + setUpForConnectedNetwork(); + when(mMockAccessPoint.isPasspoint()).thenReturn(true); + when(mMockAccessPoint.isOsuProvider()).thenReturn(false); + + displayAndResume(); + + verify(mMockSsidPref, times(1)).setSummary(SSID); + verify(mMockSsidPref, times(1)).setVisible(true); + } + + @Test + public void ssidPref_shouldHaveDetailTextSetForPasspointR2() { + setUpForConnectedNetwork(); + when(mMockAccessPoint.isPasspoint()).thenReturn(false); + when(mMockAccessPoint.isOsuProvider()).thenReturn(true); + + displayAndResume(); + + verify(mMockSsidPref, times(1)).setSummary(SSID); + verify(mMockSsidPref, times(1)).setVisible(true); + } + + @Test + public void ssidPref_shouldNotShowIfNotPasspoint() { + setUpForConnectedNetwork(); + when(mMockAccessPoint.isPasspoint()).thenReturn(false); + when(mMockAccessPoint.isOsuProvider()).thenReturn(false); + + displayAndResume(); + + verify(mMockSsidPref).setVisible(false); + } + + @Test + public void macAddressPref_shouldVisibleForConnectedNetwork() { + setUpForConnectedNetwork(); + + displayAndResume(); + + verify(mMockMacAddressPref).setVisible(true); + verify(mMockMacAddressPref).setSummary(MAC_ADDRESS); + } + + @Test + public void macAddressPref_shouldVisibleAsRandomizedForDisconnectedNetwork() { + setUpForDisconnectedNetwork(); + mMockWifiConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT; + when(mMockWifiConfig.getRandomizedMacAddress()).thenReturn(mMockMacAddress); + when(mMockMacAddress.toString()).thenReturn(RANDOMIZED_MAC_ADDRESS); + + displayAndResume(); + + verify(mMockMacAddressPref).setVisible(true); + verify(mMockMacAddressPref).setSummary(RANDOMIZED_MAC_ADDRESS); + } + + @Test + public void macAddressPref_shouldVisibleAsFactoryForDisconnectedNetwork() { + setUpForDisconnectedNetwork(); + mMockWifiConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE; + when(mMockWifiManager.getFactoryMacAddresses()) + .thenReturn(new String[]{FACTORY_MAC_ADDRESS}); + + displayAndResume(); + + verify(mMockMacAddressPref).setVisible(true); + verify(mMockMacAddressPref).setSummary(FACTORY_MAC_ADDRESS); + } + + @Test + public void ipAddressPref_shouldHaveDetailTextSetForConnectedNetwork() { + setUpForConnectedNetwork(); + mLinkProperties.addLinkAddress(Constants.IPV4_ADDR); + + displayAndResume(); + + verify(mMockIpAddressPref).setSummary(Constants.IPV4_ADDR.getAddress().getHostAddress()); + verify(mMockIpAddressPref).setVisible(true); + } + + @Test + public void ipAddressPref_shouldInvisibleForDisconnectedNetwork() { + setUpForDisconnectedNetwork(); + + displayAndResume(); + + verify(mMockIpAddressPref).setVisible(false); + } + + @Test + public void gatewayAndSubnet_shouldHaveDetailTextSetForConnectedNetwork() { + setUpForConnectedNetwork(); + mLinkProperties.addLinkAddress(Constants.IPV4_ADDR); + mLinkProperties.addRoute(Constants.IPV4_DEFAULT); + mLinkProperties.addRoute(Constants.IPV4_SUBNET); + + displayAndResume(); + + verify(mMockSubnetPref).setSummary("255.255.255.128"); + verify(mMockGatewayPref).setSummary("192.0.2.127"); + verify(mMockSubnetPref).setVisible(true); + } + + @Test + public void gatewayAndSubnet_shouldInvisibleSetForDisconnectedNetwork() { + setUpForDisconnectedNetwork(); + + displayAndResume(); + + verify(mMockSubnetPref).setVisible(false); + } + + @Test + public void dnsServersPref_shouldHaveDetailTextSetForConnectedNetwork() + throws UnknownHostException { + setUpForConnectedNetwork(); + mLinkProperties.addDnsServer(InetAddress.getByAddress(new byte[] {8, 8, 4, 4})); + mLinkProperties.addDnsServer(InetAddress.getByAddress(new byte[] {8, 8, 8, 8})); + mLinkProperties.addDnsServer(Constants.IPV6_DNS); + + displayAndResume(); + + verify(mMockDnsPref).setSummary( + "8.8.4.4\n" + "8.8.8.8\n" + Constants.IPV6_DNS.getHostAddress()); + verify(mMockDnsPref).setVisible(true); + } + + @Test + public void dnsServersPref_shouldInvisibleSetForDisconnectedNetwork() + throws UnknownHostException { + setUpForDisconnectedNetwork(); + + displayAndResume(); + + verify(mMockDnsPref).setVisible(false); + } + + @Test + public void noCurrentNetwork_shouldNotFinishActivityForConnectedNetwork() { + setUpForConnectedNetwork(); + when(mMockWifiManager.getCurrentNetwork()).thenReturn(null); + + displayAndResume(); + + verify(mMockActivity, never()).finish(); + } + + @Test + public void noLinkProperties_allIpDetailsHidden() { + setUpForConnectedNetwork(); + when(mMockConnectivityManager.getLinkProperties(mMockNetwork)).thenReturn(null); + reset(mMockIpv6Category, mMockIpAddressPref, mMockSubnetPref, mMockGatewayPref, + mMockDnsPref); + + displayAndResume(); + + verify(mMockIpv6Category).setVisible(false); + verify(mMockIpAddressPref).setVisible(false); + verify(mMockSubnetPref).setVisible(false); + verify(mMockGatewayPref).setVisible(false); + verify(mMockDnsPref).setVisible(false); + verify(mMockIpv6Category, never()).setVisible(true); + verify(mMockIpAddressPref, never()).setVisible(true); + verify(mMockSubnetPref, never()).setVisible(true); + verify(mMockGatewayPref, never()).setVisible(true); + verify(mMockDnsPref, never()).setVisible(true); + } + + @Test + public void disconnectedNetwork_allIpDetailsHidden() { + setUpForDisconnectedNetwork(); + reset(mMockIpv6Category, mMockIpAddressPref, mMockSubnetPref, mMockGatewayPref, + mMockDnsPref); + + displayAndResume(); + + verify(mMockIpv6Category).setVisible(false); + verify(mMockIpAddressPref).setVisible(false); + verify(mMockSubnetPref).setVisible(false); + verify(mMockGatewayPref).setVisible(false); + verify(mMockDnsPref).setVisible(false); + verify(mMockIpv6Category, never()).setVisible(true); + verify(mMockIpAddressPref, never()).setVisible(true); + verify(mMockSubnetPref, never()).setVisible(true); + verify(mMockGatewayPref, never()).setVisible(true); + verify(mMockDnsPref, never()).setVisible(true); + } + + // Convenience method to convert a LinkAddress to a string without a prefix length. + private String asString(LinkAddress l) { + return l.getAddress().getHostAddress(); + } + + // Pretend that the NetworkCallback was triggered with a new copy of lp. We need to create a + // new copy because the code only updates if !mLinkProperties.equals(lp). + private void updateLinkProperties(LinkProperties lp) { + mCallbackCaptor.getValue().onLinkPropertiesChanged(mMockNetwork, new LinkProperties(lp)); + } + + private void updateNetworkCapabilities(NetworkCapabilities nc) { + mCallbackCaptor.getValue().onCapabilitiesChanged(mMockNetwork, new NetworkCapabilities(nc)); + } + + private NetworkCapabilities makeNetworkCapabilities() { + NetworkCapabilities nc = new NetworkCapabilities(); + nc.clearAll(); + nc.addTransportType(NetworkCapabilities.TRANSPORT_WIFI); + return nc; + } + + private void verifyDisplayedIpv6Addresses(InOrder inOrder, LinkAddress... addresses) { + String text = Arrays.stream(addresses) + .map(address -> asString(address)) + .collect(Collectors.joining("\n")); + inOrder.verify(mMockIpv6AddressesPref).setSummary(text); + } + + @Test + public void onLinkPropertiesChanged_updatesFields() { + setUpForConnectedNetwork(); + displayAndResume(); + + InOrder inOrder = inOrder(mMockIpAddressPref, mMockGatewayPref, mMockSubnetPref, + mMockDnsPref, mMockIpv6Category, mMockIpv6AddressesPref); + + LinkProperties lp = new LinkProperties(); + + lp.addLinkAddress(Constants.IPV6_LINKLOCAL); + updateLinkProperties(lp); + verifyDisplayedIpv6Addresses(inOrder, Constants.IPV6_LINKLOCAL); + inOrder.verify(mMockIpv6Category).setVisible(true); + + lp.addRoute(Constants.IPV4_DEFAULT); + updateLinkProperties(lp); + inOrder.verify(mMockGatewayPref).setSummary(Constants.IPV4_GATEWAY.getHostAddress()); + inOrder.verify(mMockGatewayPref).setVisible(true); + + lp.addLinkAddress(Constants.IPV4_ADDR); + lp.addRoute(Constants.IPV4_SUBNET); + updateLinkProperties(lp); + inOrder.verify(mMockIpAddressPref).setSummary(asString(Constants.IPV4_ADDR)); + inOrder.verify(mMockIpAddressPref).setVisible(true); + inOrder.verify(mMockSubnetPref).setSummary("255.255.255.128"); + inOrder.verify(mMockSubnetPref).setVisible(true); + + lp.addLinkAddress(Constants.IPV6_GLOBAL1); + lp.addLinkAddress(Constants.IPV6_GLOBAL2); + updateLinkProperties(lp); + verifyDisplayedIpv6Addresses(inOrder, + Constants.IPV6_LINKLOCAL, + Constants.IPV6_GLOBAL1, + Constants.IPV6_GLOBAL2); + + lp.removeLinkAddress(Constants.IPV6_GLOBAL1); + updateLinkProperties(lp); + verifyDisplayedIpv6Addresses(inOrder, + Constants.IPV6_LINKLOCAL, + Constants.IPV6_GLOBAL2); + + lp.addDnsServer(Constants.IPV6_DNS); + updateLinkProperties(lp); + inOrder.verify(mMockDnsPref).setSummary(Constants.IPV6_DNS.getHostAddress()); + inOrder.verify(mMockDnsPref).setVisible(true); + + lp.addDnsServer(Constants.IPV4_DNS1); + lp.addDnsServer(Constants.IPV4_DNS2); + updateLinkProperties(lp); + inOrder.verify(mMockDnsPref).setSummary( + Constants.IPV6_DNS.getHostAddress() + "\n" + + Constants.IPV4_DNS1.getHostAddress() + "\n" + + Constants.IPV4_DNS2.getHostAddress()); + inOrder.verify(mMockDnsPref).setVisible(true); + } + + @Test + public void onCapabilitiesChanged_callsRefreshIfNecessary() { + setUpForConnectedNetwork(); + NetworkCapabilities nc = makeNetworkCapabilities(); + when(mMockConnectivityManager.getNetworkCapabilities(mMockNetwork)) + .thenReturn(new NetworkCapabilities(nc)); + + String summary = "Connected, no Internet"; + when(mMockAccessPoint.getSettingsSummary(true /*convertSavedAsDisconnected*/)) + .thenReturn(summary); + + InOrder inOrder = inOrder(mMockHeaderController); + displayAndResume(); + inOrder.verify(mMockHeaderController).setSummary(summary); + + // Check that an irrelevant capability update does not update the access point summary, as + // doing so could cause unnecessary jank... + summary = "Connected"; + when(mMockAccessPoint.getSettingsSummary(true /*convertSavedAsDisconnected*/)) + .thenReturn(summary); + updateNetworkCapabilities(nc); + inOrder.verify(mMockHeaderController, never()).setSummary(any(CharSequence.class)); + + // ... but that if the network validates, then we do refresh. + nc.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); + updateNetworkCapabilities(nc); + inOrder.verify(mMockHeaderController).setSummary(summary); + + summary = "Connected, no Internet"; + when(mMockAccessPoint.getSettingsSummary(true /*convertSavedAsDisconnected*/)) + .thenReturn(summary); + + // Another irrelevant update won't cause the UI to refresh... + updateNetworkCapabilities(nc); + inOrder.verify(mMockHeaderController, never()).setSummary(any(CharSequence.class)); + + // ... but if the network is no longer validated, then we display "connected, no Internet". + nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); + updateNetworkCapabilities(nc); + inOrder.verify(mMockHeaderController).setSummary(summary); + + // UI will be refreshed when private DNS is broken. + summary = "Private DNS server cannot be accessed"; + when(mMockAccessPoint.getSettingsSummary(true /* convertSavedAsDisconnected */)) + .thenReturn(summary); + nc.setPrivateDnsBroken(true); + updateNetworkCapabilities(nc); + inOrder.verify(mMockHeaderController).setSummary(summary); + + // UI will be refreshed when device connects to a partial connectivity network. + summary = "Limited connection"; + when(mMockAccessPoint.getSettingsSummary(true /*convertSavedAsDisconnected*/)) + .thenReturn(summary); + nc.addCapability(NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY); + updateNetworkCapabilities(nc); + inOrder.verify(mMockHeaderController).setSummary(summary); + + // Although UI will be refreshed when network become validated. The Settings should + // continue to display "Limited connection" if network still provides partial connectivity. + nc.addCapability(NetworkCapabilities.NET_CAPABILITY_VALIDATED); + updateNetworkCapabilities(nc); + inOrder.verify(mMockHeaderController).setSummary(summary); + } + + @Test + public void canForgetNetwork_shouldInvisibleIfWithoutConfiguration() { + setUpForConnectedNetwork(); + when(mMockAccessPoint.getConfig()).thenReturn(null); + mController = newWifiDetailPreferenceController2(); + + displayAndResume(); + + verify(mMockButtonsPref).setButton1Visible(false); + } + + @Test + public void canForgetNetwork_ephemeral() { + setUpForConnectedNetwork(); + when(mMockWifiInfo.isEphemeral()).thenReturn(true); + when(mMockAccessPoint.getConfig()).thenReturn(null); + + displayAndResume(); + + verify(mMockButtonsPref).setButton1Visible(true); + } + + @Test + public void canForgetNetwork_saved() { + setUpForConnectedNetwork(); + displayAndResume(); + + verify(mMockButtonsPref).setButton1Visible(true); + } + + @Test + public void canForgetNetwork_lockedDown() { + setUpForConnectedNetwork(); + lockDownNetwork(); + + displayAndResume(); + + verify(mMockButtonsPref).setButton1Visible(false); + } + + @Test + public void canShareNetwork_shouldInvisibleIfWithoutConfiguration() { + setUpForConnectedNetwork(); + when(mMockAccessPoint.getConfig()).thenReturn(null); + + displayAndResume(); + + verify(mMockButtonsPref).setButton4Visible(false); + } + + @Test + public void canModifyNetwork_saved() { + setUpForConnectedNetwork(); + assertThat(mController.canModifyNetwork()).isTrue(); + } + + @Test + public void canModifyNetwork_lockedDown() { + setUpForConnectedNetwork(); + lockDownNetwork(); + + assertThat(mController.canModifyNetwork()).isFalse(); + } + + /** + * Pretends that current network is locked down by device owner. + */ + private void lockDownNetwork() { + final int doUserId = 123; + final int doUid = 1234; + String doPackage = "some.package"; + + mMockWifiConfig.creatorUid = doUid; + ComponentName doComponent = new ComponentName(doPackage, "some.Class"); + try { + when(mMockPackageManager.getPackageUidAsUser(Matchers.anyString(), Matchers.anyInt())) + .thenReturn(doUid); + } catch (PackageManager.NameNotFoundException e) { + //do nothing + } + ShadowDevicePolicyManager.getShadow().setDeviceOwnerComponentOnAnyUser(doComponent); + ShadowDevicePolicyManager.getShadow().setDeviceOwnerUserId(doUserId); + + Settings.Global.putInt(mContext.getContentResolver(), + Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 1); + } + + @Test + public void forgetNetwork_ephemeral() { + setUpForConnectedNetwork(); + String ssid = "ssid"; + when(mMockWifiInfo.isEphemeral()).thenReturn(true); + when(mMockWifiInfo.getSSID()).thenReturn(ssid); + + displayAndResume(); + mForgetClickListener.getValue().onClick(null); + + verify(mMockWifiManager).disableEphemeralNetwork(ssid); + verify(mMockMetricsFeatureProvider) + .action(mMockActivity, MetricsProto.MetricsEvent.ACTION_WIFI_FORGET); + } + + @Test + public void forgetNetwork_saved() { + setUpForConnectedNetwork(); + mMockWifiConfig.networkId = 5; + + mController.displayPreference(mMockScreen); + mForgetClickListener.getValue().onClick(null); + + verify(mMockWifiManager).forget(mMockWifiConfig.networkId, null); + verify(mMockMetricsFeatureProvider) + .action(mMockActivity, MetricsProto.MetricsEvent.ACTION_WIFI_FORGET); + } + + @Test + public void forgetNetwork_shouldShowDialog() { + setUpForConnectedNetwork(); + final WifiDetailPreferenceController2 spyController = spy(mController); + + mMockWifiConfig.networkId = 5; + when(mMockAccessPoint.isPasspoint()).thenReturn(true); + when(mMockAccessPoint.getPasspointFqdn()).thenReturn(FQDN); + spyController.displayPreference(mMockScreen); + + mForgetClickListener.getValue().onClick(null); + + verify(mMockWifiManager, times(0)).removePasspointConfiguration(FQDN); + verify(mMockMetricsFeatureProvider, times(0)) + .action(mMockActivity, MetricsProto.MetricsEvent.ACTION_WIFI_FORGET); + verify(spyController).showConfirmForgetDialog(); + } + + @Test + public void networkStateChangedIntent_shouldRefetchInfo() { + setUpForConnectedNetwork(); + + displayAndResume(); + + verify(mMockConnectivityManager, times(1)).getNetworkInfo(any(Network.class)); + verify(mMockWifiManager, times(1)).getConnectionInfo(); + + mContext.sendBroadcast(new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION)); + + verify(mMockConnectivityManager, times(2)).getNetworkInfo(any(Network.class)); + verify(mMockWifiManager, times(2)).getConnectionInfo(); + } + + @Test + public void networkStateChangedIntent_shouldRefetchInfoForConnectedNetwork() { + setUpForConnectedNetwork(); + + displayAndResume(); + + verify(mMockConnectivityManager, times(1)).getNetworkInfo(any(Network.class)); + verify(mMockWifiManager, times(1)).getConnectionInfo(); + + mContext.sendBroadcast(new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION)); + + verify(mMockConnectivityManager, times(2)).getNetworkInfo(any(Network.class)); + verify(mMockWifiManager, times(2)).getConnectionInfo(); + } + + @Test + public void rssiChangedIntent_shouldRefetchInfo() { + setUpForConnectedNetwork(); + + displayAndResume(); + + verify(mMockConnectivityManager, times(1)).getNetworkInfo(any(Network.class)); + verify(mMockWifiManager, times(1)).getConnectionInfo(); + + mContext.sendBroadcast(new Intent(WifiManager.RSSI_CHANGED_ACTION)); + + verify(mMockConnectivityManager, times(2)).getNetworkInfo(any(Network.class)); + verify(mMockWifiManager, times(2)).getConnectionInfo(); + } + + @Test + public void rssiChangedIntent_shouldRefetchInfoForConnectedNetwork() { + setUpForConnectedNetwork(); + displayAndResume(); + + verify(mMockConnectivityManager, times(1)).getNetworkInfo(any(Network.class)); + verify(mMockWifiManager, times(1)).getConnectionInfo(); + + mContext.sendBroadcast(new Intent(WifiManager.RSSI_CHANGED_ACTION)); + + verify(mMockConnectivityManager, times(2)).getNetworkInfo(any(Network.class)); + verify(mMockWifiManager, times(2)).getConnectionInfo(); + } + + @Test + public void networkDisconnectedState_shouldNotFinishActivityForConnectedNetwork() { + setUpForConnectedNetwork(); + + displayAndResume(); + + when(mMockConnectivityManager.getNetworkInfo(any(Network.class))).thenReturn(null); + mContext.sendBroadcast(new Intent(WifiManager.NETWORK_STATE_CHANGED_ACTION)); + + verify(mMockActivity, never()).finish(); + } + + @Test + public void networkOnLost_shouldNotFinishActivityForConnectedNetwork() { + setUpForConnectedNetwork(); + + displayAndResume(); + + mCallbackCaptor.getValue().onLost(mMockNetwork); + + verify(mMockActivity, never()).finish(); + } + + @Test + public void ipv6AddressPref_shouldHaveHostAddressTextSet() { + setUpForConnectedNetwork(); + mLinkProperties.addLinkAddress(Constants.IPV6_LINKLOCAL); + mLinkProperties.addLinkAddress(Constants.IPV6_GLOBAL1); + mLinkProperties.addLinkAddress(Constants.IPV6_GLOBAL2); + + displayAndResume(); + + String expectedAddresses = String.join("\n", + asString(Constants.IPV6_LINKLOCAL), + asString(Constants.IPV6_GLOBAL1), + asString(Constants.IPV6_GLOBAL2)); + + verify(mMockIpv6AddressesPref).setSummary(expectedAddresses); + } + + @Test + public void ipv6AddressPref_shouldNotBeSelectable() { + setUpForConnectedNetwork(); + mLinkProperties.addLinkAddress(Constants.IPV6_GLOBAL2); + + displayAndResume(); + + assertThat(mMockIpv6AddressesPref.isSelectable()).isFalse(); + } + + @Test + public void captivePortal_shouldShowSignInButton() { + setUpForConnectedNetwork(); + + InOrder inOrder = inOrder(mMockButtonsPref); + + displayAndResume(); + + inOrder.verify(mMockButtonsPref).setButton2Visible(false); + + NetworkCapabilities nc = makeNetworkCapabilities(); + updateNetworkCapabilities(nc); + inOrder.verify(mMockButtonsPref).setButton2Visible(false); + + nc.addCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL); + updateNetworkCapabilities(nc); + inOrder.verify(mMockButtonsPref).setButton2Visible(true); + + nc.removeCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL); + updateNetworkCapabilities(nc); + inOrder.verify(mMockButtonsPref).setButton2Visible(false); + } + + @Test + public void testSignInButton_shouldStartCaptivePortalApp() { + setUpForConnectedNetwork(); + + displayAndResume(); + + ArgumentCaptor captor = ArgumentCaptor.forClass(OnClickListener.class); + verify(mMockButtonsPref).setButton2OnClickListener(captor.capture()); + captor.getValue().onClick(null); + verify(mMockConnectivityManager).startCaptivePortalApp(mMockNetwork); + verify(mMockMetricsFeatureProvider) + .action(mMockActivity, MetricsProto.MetricsEvent.ACTION_WIFI_SIGNIN); + } + + @Test + public void testSignInButton_shouldHideSignInButtonForDisconnectedNetwork() { + setUpForDisconnectedNetwork(); + NetworkCapabilities nc = makeNetworkCapabilities(); + nc.addCapability(NetworkCapabilities.NET_CAPABILITY_CAPTIVE_PORTAL); + when(mMockConnectivityManager.getNetworkCapabilities(mMockNetwork)) + .thenReturn(new NetworkCapabilities(nc)); + + // verify onResume + displayAndResume(); + + verify(mMockButtonsPref, never()).setButton2Visible(true); + verify(mMockButtonsPref).setButton2Visible(false); + + // verify onCapabilitiesChanged + updateNetworkCapabilities(nc); + + verify(mMockButtonsPref, never()).setButton2Visible(true); + verify(mMockButtonsPref).setButton2Visible(false); + } + + @Test + public void testConnectButton_shouldInvisibleForConnectNetwork() { + setUpForConnectedNetwork(); + + displayAndResume(); + + verify(mMockButtonsPref, times(1)).setButton3Visible(false); + } + + @Test + public void testConnectButton_shouldVisibleForDisconnectNetwork() { + setUpForDisconnectedNetwork(); + + displayAndResume(); + + verify(mMockButtonsPref, times(1)).setButton3Visible(true); + verify(mMockButtonsPref, times(1)).setButton3Text(R.string.wifi_connect); + } + + private void setUpForToast() { + Resources res = mContext.getResources(); + when(mMockActivity.getResources()).thenReturn(res); + } + + @Test + public void testConnectButton_clickConnect_displayAsSuccess() { + setUpForDisconnectedNetwork(); + when(mMockWifiManager.isWifiEnabled()).thenReturn(true); + InOrder inOrder = inOrder(mMockButtonsPref); + String label = "title"; + when(mMockAccessPoint.getTitle()).thenReturn(label); + setUpForToast(); + + displayAndResume(); + + // check connect button exist + verifyConnectBtnSetUpAsVisible(inOrder); + + // click connect button + mController.connectNetwork(); + + // check display button as connecting + verify(mMockWifiManager, times(1)).connect(anyInt(), any(WifiManager.ActionListener.class)); + verifyConnectBtnSetUpAsConnecting(inOrder); + + // update as connected + when(mMockAccessPoint.isActive()).thenReturn(true); + mController.updateAccessPoint(); + + // check connect button invisible, be init as default state and toast success message + verifyConnectBtnBeInitAsDefault(inOrder); + inOrder.verify(mMockButtonsPref, times(1)).setButton3Visible(false); + assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo( + mContext.getString(R.string.wifi_connected_to_message, label)); + } + + @Test + public void testConnectButton_clickConnectButFailed_displayFailMessage() { + setUpForDisconnectedNetwork(); + ArgumentCaptor connectListenerCaptor = + ArgumentCaptor.forClass(WifiManager.ActionListener.class); + when(mMockWifiManager.isWifiEnabled()).thenReturn(true); + InOrder inOrder = inOrder(mMockButtonsPref); + setUpForToast(); + + displayAndResume(); + + // check connect button exist + verifyConnectBtnSetUpAsVisible(inOrder); + + // click connect button + mController.connectNetwork(); + + // check display button as connecting + verify(mMockWifiManager, times(1)).connect(anyInt(), connectListenerCaptor.capture()); + verifyConnectBtnSetUpAsConnecting(inOrder); + + // update as failed + connectListenerCaptor.getValue().onFailure(-1); + + // check connect button visible, be init as default and toast failed message + verifyConnectBtnBeInitAsDefault(inOrder); + inOrder.verify(mMockButtonsPref, times(1)).setButton3Visible(true); + assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo( + mContext.getString(R.string.wifi_failed_connect_message)); + } + + private void verifyConnectBtnSetUpAsVisible(InOrder inOrder) { + inOrder.verify(mMockButtonsPref, times(1)).setButton3Text(R.string.wifi_connect); + inOrder.verify(mMockButtonsPref, times(1)).setButton3Icon(R.drawable.ic_settings_wireless); + inOrder.verify(mMockButtonsPref, times(1)).setButton3Visible(true); + } + + private void verifyConnectBtnSetUpAsConnecting(InOrder inOrder) { + inOrder.verify(mMockButtonsPref, times(1)).setButton3Text(R.string.wifi_connecting); + inOrder.verify(mMockButtonsPref, times(1)).setButton3Enabled(false); + } + + private void verifyConnectBtnBeInitAsDefault(InOrder inOrder) { + inOrder.verify(mMockButtonsPref, times(1)).setButton3Text(R.string.wifi_connect); + inOrder.verify(mMockButtonsPref, times(1)).setButton3Icon(R.drawable.ic_settings_wireless); + inOrder.verify(mMockButtonsPref, times(1)).setButton3Enabled(true); + } + + @Test + public void testConnectButton_clickConnectButTimeout_displayFailMessage() { + setUpForDisconnectedNetwork(); + when(mMockWifiManager.isWifiEnabled()).thenReturn(true); + InOrder inOrder = inOrder(mMockButtonsPref); + setUpForToast(); + + displayAndResume(); + + // check connect button exist + verifyConnectBtnSetUpAsVisible(inOrder); + + // click connect button + mController.connectNetwork(); + + // check display button as connecting + verify(mMockWifiManager, times(1)).connect(anyInt(), any(WifiManager.ActionListener.class)); + verifyConnectBtnSetUpAsConnecting(inOrder); + + // update as failed + mController.sTimer.onFinish(); + + // check connect button visible, be init as default and toast failed message + verifyConnectBtnBeInitAsDefault(inOrder); + inOrder.verify(mMockButtonsPref, times(1)).setButton3Visible(true); + assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo( + mContext.getString(R.string.wifi_failed_connect_message)); + } + + @Test + public void testConnectButton_clickConnectButTimeout_displayNotInRangeMessage() { + setUpForNotInRangeNetwork(); + when(mMockWifiManager.isWifiEnabled()).thenReturn(true); + InOrder inOrder = inOrder(mMockButtonsPref); + setUpForToast(); + + displayAndResume(); + + // check connect button exist + verifyConnectBtnSetUpAsVisible(inOrder); + + // click connect button + mController.connectNetwork(); + + // check display button as connecting + verify(mMockWifiManager, times(1)).connect(anyInt(), any(WifiManager.ActionListener.class)); + verifyConnectBtnSetUpAsConnecting(inOrder); + + // update as failed + mController.sTimer.onFinish(); + + // check connect button visible, be init as default and toast failed message + verifyConnectBtnBeInitAsDefault(inOrder); + inOrder.verify(mMockButtonsPref, times(1)).setButton3Visible(true); + assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo( + mContext.getString(R.string.wifi_not_in_range_message)); + } + + @Test + public void testConnectButton_clickConnectWhenWiFiDisabled_displaySuccessMessage() { + setUpForDisconnectedNetwork(); + when(mMockWifiManager.isWifiEnabled()).thenReturn(false); // wifi disabled + InOrder inOrder = inOrder(mMockButtonsPref); + String label = "title"; + when(mMockAccessPoint.getTitle()).thenReturn(label); + setUpForToast(); + + displayAndResume(); + + // check connect button exist + verifyConnectBtnSetUpAsVisible(inOrder); + + // click connect button + mController.connectNetwork(); + + // check turn on Wi-Fi, display button as connecting and toast turn on Wi-Fi message + verify(mMockWifiManager, times(1)).setWifiEnabled(true); + verifyConnectBtnSetUpAsConnecting(inOrder); + assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo( + mContext.getString(R.string.wifi_turned_on_message)); + + // notify Wi-Fi enabled + mController.mWifiListener.onWifiStateChanged(WifiManager.WIFI_STATE_ENABLED); + + // check had connect network and icon display as expected + verify(mMockWifiManager, times(1)).connect(anyInt(), any(WifiManager.ActionListener.class)); + verifyConnectBtnSetUpAsConnecting(inOrder); + + // update as connected + when(mMockAccessPoint.isActive()).thenReturn(true); + mController.updateAccessPoint(); + + // check connect button invisible, be init as default state and toast success message + verifyConnectBtnBeInitAsDefault(inOrder); + inOrder.verify(mMockButtonsPref, times(1)).setButton3Visible(false); + assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo( + mContext.getString(R.string.wifi_connected_to_message, label)); + } + + @Test + public void testConnectButton_clickConnectWhenWiFiDisabled_failedToConnectWiFi() { + setUpForDisconnectedNetwork(); + when(mMockWifiManager.isWifiEnabled()).thenReturn(false); // wifi disabled + InOrder inOrder = inOrder(mMockButtonsPref); + setUpForToast(); + + displayAndResume(); + + // check connect button exist + verifyConnectBtnSetUpAsVisible(inOrder); + + // click connect button + mController.connectNetwork(); + + // check turn on Wi-Fi, display button as connecting and toast turn on Wi-Fi message + verify(mMockWifiManager, times(1)).setWifiEnabled(true); + verifyConnectBtnSetUpAsConnecting(inOrder); + assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo( + mContext.getString(R.string.wifi_turned_on_message)); + + // notify Wi-Fi enabled + mController.mWifiListener.onWifiStateChanged(WifiManager.WIFI_STATE_ENABLED); + + // check had connect network and icon display as expected + verify(mMockWifiManager, times(1)).connect(anyInt(), any(WifiManager.ActionListener.class)); + verifyConnectBtnSetUpAsConnecting(inOrder); + + // update as failed + mController.sTimer.onFinish(); + + // check connect button visible, be init as default and toast failed message + verifyConnectBtnBeInitAsDefault(inOrder); + inOrder.verify(mMockButtonsPref, times(1)).setButton3Visible(true); + assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo( + mContext.getString(R.string.wifi_failed_connect_message)); + } + + @Test + public void + testConnectButton_clickConnectWhenWiFiDisabled_failedToConnectWifiBecauseNotInRange() { + setUpForNotInRangeNetwork(); + when(mMockWifiManager.isWifiEnabled()).thenReturn(false); // wifi disabled + InOrder inOrder = inOrder(mMockButtonsPref); + setUpForToast(); + + displayAndResume(); + + // check connect button exist + verifyConnectBtnSetUpAsVisible(inOrder); + + // click connect button + mController.connectNetwork(); + + // check turn on Wi-Fi, display button as connecting and toast turn on Wi-Fi message + verify(mMockWifiManager, times(1)).setWifiEnabled(true); + verifyConnectBtnSetUpAsConnecting(inOrder); + assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo( + mContext.getString(R.string.wifi_turned_on_message)); + + // notify Wi-Fi enabled + mController.mWifiListener.onWifiStateChanged(WifiManager.WIFI_STATE_ENABLED); + + // check had connect network and icon display as expected + verify(mMockWifiManager, times(1)).connect(anyInt(), any(WifiManager.ActionListener.class)); + verifyConnectBtnSetUpAsConnecting(inOrder); + + // update as failed + mController.sTimer.onFinish(); + + // check connect button visible, be init as default and toast failed message + verifyConnectBtnBeInitAsDefault(inOrder); + inOrder.verify(mMockButtonsPref, times(1)).setButton3Visible(true); + assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo( + mContext.getString(R.string.wifi_not_in_range_message)); + } + + @Test + public void testConnectButton_clickConnectWhenWiFiDisabled_failedToEnableWifi() { + setUpForDisconnectedNetwork(); + when(mMockWifiManager.isWifiEnabled()).thenReturn(false); // wifi disabled + InOrder inOrder = inOrder(mMockButtonsPref); + setUpForToast(); + + displayAndResume(); + + // check connect button exist + verifyConnectBtnSetUpAsVisible(inOrder); + + // click connect button + mController.connectNetwork(); + + // check turn on Wi-Fi, display button as connecting and toast turn on Wi-Fi message + verify(mMockWifiManager, times(1)).setWifiEnabled(true); + verifyConnectBtnSetUpAsConnecting(inOrder); + assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo( + mContext.getString(R.string.wifi_turned_on_message)); + + // notify turn on Wi-Fi failed + mController.sTimer.onFinish(); + + // check connect button visible, be init as default and toast failed message + verifyConnectBtnBeInitAsDefault(inOrder); + inOrder.verify(mMockButtonsPref, times(1)).setButton3Visible(true); + assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo( + mContext.getString(R.string.wifi_failed_connect_message)); + } + + @Test + public void testConnectButton_clickConnectAndBackKey_ignoreTimeoutEvent() { + setUpForDisconnectedNetwork(); + when(mMockWifiManager.isWifiEnabled()).thenReturn(true); + InOrder inOrder = inOrder(mMockButtonsPref); + setUpForToast(); + + displayAndResume(); + + // check connect button exist + verifyConnectBtnSetUpAsVisible(inOrder); + + // click connect button + mController.connectNetwork(); + + // check display button as connecting + verify(mMockWifiManager, times(1)).connect(anyInt(), any(WifiManager.ActionListener.class)); + verifyConnectBtnSetUpAsConnecting(inOrder); + + // leave detail page + when(mMockFragment.getActivity()).thenReturn(null); + + // timeout happened + mController.sTimer.onFinish(); + + // check connect button visible, be init as default and toast failed message + inOrder.verify(mMockButtonsPref, never()).setButton3Text(R.string.wifi_connect); + inOrder.verify(mMockButtonsPref, never()).setButton3Icon(R.drawable.ic_settings_wireless); + inOrder.verify(mMockButtonsPref, never()).setButton3Enabled(true); + inOrder.verify(mMockButtonsPref, never()).setButton3Visible(true); + assertThat(ShadowToast.shownToastCount()).isEqualTo(0); + } + + @Test + public void updateAccessPoint_returnFalseForNothingChanged() { + setUpForDisconnectedNetwork(); + + displayAndResume(); + boolean changed = mController.updateAccessPoint(); + + assertThat(changed).isFalse(); + } + + @Test + public void updateAccessPoint_returnTrueForSignalLevelChanged() { + setUpForDisconnectedNetwork(); + + displayAndResume(); + + // Level changed + when(mMockAccessPoint.getLevel()).thenReturn(LEVEL + 1); + boolean changed = mController.updateAccessPoint(); + + assertThat(changed).isTrue(); + } + + @Test + public void updateAccessPoint_returnTrueForChangeAsNotInRange() { + setUpForDisconnectedNetwork(); + + displayAndResume(); + + // change as not in range + when(mMockAccessPoint.matches(any(AccessPoint.class))).thenReturn(false); + boolean changed = mController.updateAccessPoint(); + + assertThat(changed).isTrue(); + } + + @Test + public void updateAccessPoint_returnTrueForChangeAsInRange() { + setUpForNotInRangeNetwork(); + + displayAndResume(); + + // change as in range + when(mMockAccessPoint.matches(any(AccessPoint.class))).thenReturn(true); + boolean changed = mController.updateAccessPoint(); + + assertThat(changed).isTrue(); + } + + @Test + public void updateAccessPoint_returnTrueForChangeAsConnected() { + setUpForDisconnectedNetwork(); + + displayAndResume(); + + // change as connected + when(mMockAccessPoint.isActive()).thenReturn(true); + boolean changed = mController.updateAccessPoint(); + + assertThat(changed).isTrue(); + } + + @Test + public void updateAccessPoint_returnTrueForChangeAsDisconnected() { + setUpForConnectedNetwork(); + + displayAndResume(); + + // change as disconnected + when(mMockAccessPoint.isActive()).thenReturn(false); + boolean changed = mController.updateAccessPoint(); + + assertThat(changed).isTrue(); + } + + @Test + public void updateAccessPoint_returnTrueForAccessPointUpdated() { + setUpForConnectedNetwork(); + + displayAndResume(); + + // change as disconnected + when(mMockAccessPoint.update(mMockWifiConfig, mMockWifiInfo, mMockNetworkInfo)) + .thenReturn(true); + boolean changed = mController.updateAccessPoint(); + + assertThat(changed).isTrue(); + } + + @Test + public void testRefreshRssiViews_shouldNotUpdateIfLevelIsSameForConnectedNetwork() { + setUpForConnectedNetwork(); + displayAndResume(); + + mContext.sendBroadcast(new Intent(WifiManager.RSSI_CHANGED_ACTION)); + + verify(mMockAccessPoint, times(3)).getLevel(); + verify(mMockIconInjector, times(1)).getIcon(anyInt()); + } + + @Test + public void testRefreshRssiViews_shouldUpdateOnLevelChangeForConnectedNetwork() { + setUpForConnectedNetwork(); + displayAndResume(); + + when(mMockAccessPoint.getLevel()).thenReturn(0); + mContext.sendBroadcast(new Intent(WifiManager.RSSI_CHANGED_ACTION)); + + verify(mMockAccessPoint, times(4)).getLevel(); + verify(mMockIconInjector, times(2)).getIcon(anyInt()); + } + + @Test + public void testRefreshRssiViews_shouldNotUpdateForNotInRangeNetwork() { + setUpForNotInRangeNetwork(); + + displayAndResume(); + + when(mMockAccessPoint.getLevel()).thenReturn(0); + mContext.sendBroadcast(new Intent(WifiManager.RSSI_CHANGED_ACTION)); + + verify(mMockSignalStrengthPref, times(2)).setVisible(false); + } + + @Test + public void testRedrawIconForHeader_shouldEnlarge() { + setUpForConnectedNetwork(); + ArgumentCaptor drawableCaptor = + ArgumentCaptor.forClass(BitmapDrawable.class); + Drawable original = mContext.getDrawable(Utils.getWifiIconResource(LEVEL)).mutate(); + when(mMockIconInjector.getIcon(anyInt())).thenReturn(original); + + displayAndResume(); + + verify(mMockHeaderController, times(1)).setIcon(drawableCaptor.capture()); + + int expectedSize = mContext.getResources().getDimensionPixelSize( + R.dimen.wifi_detail_page_header_image_size); + BitmapDrawable icon = drawableCaptor.getValue(); + assertThat(icon.getMinimumWidth()).isEqualTo(expectedSize); + assertThat(icon.getMinimumHeight()).isEqualTo(expectedSize); + } + + @Test + public void testRedrawIconForHeader_shouldEnlargeForDisconnectedNetwork() { + setUpForDisconnectedNetwork(); + ArgumentCaptor drawableCaptor = + ArgumentCaptor.forClass(BitmapDrawable.class); + Drawable original = mContext.getDrawable(Utils.getWifiIconResource(LEVEL)).mutate(); + when(mMockIconInjector.getIcon(anyInt())).thenReturn(original); + + displayAndResume(); + + verify(mMockHeaderController, times(1)).setIcon(drawableCaptor.capture()); + + int expectedSize = mContext.getResources().getDimensionPixelSize( + R.dimen.wifi_detail_page_header_image_size); + BitmapDrawable icon = drawableCaptor.getValue(); + assertThat(icon.getMinimumWidth()).isEqualTo(expectedSize); + assertThat(icon.getMinimumHeight()).isEqualTo(expectedSize); + } + + @Test + public void testRedrawIconForHeader_shouldNotEnlargeIfNotVectorDrawable() { + setUpForConnectedNetwork(); + ArgumentCaptor drawableCaptor = + ArgumentCaptor.forClass(ColorDrawable.class); + + displayAndResume(); + + verify(mMockHeaderController, times(1)).setIcon(drawableCaptor.capture()); + ColorDrawable icon = drawableCaptor.getValue(); + assertThat(icon).isNotNull(); + } + + @Test + public void checkMacTitle_whenPrivacyRandomizedMac_shouldBeRandom() { + setUpForDisconnectedNetwork(); + mMockWifiConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_PERSISTENT; + when(mMockWifiConfig.getRandomizedMacAddress()).thenReturn(mMockMacAddress); + when(mMockMacAddress.toString()).thenReturn(RANDOMIZED_MAC_ADDRESS); + + displayAndResume(); + + verify(mMockMacAddressPref).setTitle(R.string.wifi_advanced_randomized_mac_address_title); + } + + @Test + public void checkMacTitle_whenPrivacyDeviceMac_shouldBeFactory() { + setUpForDisconnectedNetwork(); + mMockWifiConfig.macRandomizationSetting = WifiConfiguration.RANDOMIZATION_NONE; + when(mMockWifiConfig.getRandomizedMacAddress()).thenReturn(mMockMacAddress); + when(mMockWifiManager.getFactoryMacAddresses()) + .thenReturn(new String[]{FACTORY_MAC_ADDRESS}); + + displayAndResume(); + + verify(mMockMacAddressPref).setTitle(R.string.wifi_advanced_device_mac_address_title); + } + + @Test + @Ignore + public void entityHeader_expiredPasspointR1_shouldHandleExpiration() { + setUpForDisconnectedNetwork(); + when(mMockAccessPoint.isPasspoint()).thenReturn(true); + when(mMockAccessPoint.isPasspointConfigurationR1()).thenReturn(true); + when(mMockAccessPoint.isExpired()).thenReturn(true); + String expireSummary = mContext.getResources().getString( + com.android.settingslib.R.string.wifi_passpoint_expired); + + displayAndResume(); + + verify(mMockButtonsPref).setButton3Visible(false); + verify(mMockHeaderController).setSummary(expireSummary); + } + + private ActionButtonsPreference createMock() { + final ActionButtonsPreference pref = mock(ActionButtonsPreference.class); + when(pref.setButton1Text(anyInt())).thenReturn(pref); + when(pref.setButton1Icon(anyInt())).thenReturn(pref); + when(pref.setButton1Enabled(anyBoolean())).thenReturn(pref); + when(pref.setButton1Visible(anyBoolean())).thenReturn(pref); + when(pref.setButton1OnClickListener(any(View.OnClickListener.class))).thenReturn(pref); + + when(pref.setButton2Text(anyInt())).thenReturn(pref); + when(pref.setButton2Icon(anyInt())).thenReturn(pref); + when(pref.setButton2Enabled(anyBoolean())).thenReturn(pref); + when(pref.setButton2Visible(anyBoolean())).thenReturn(pref); + when(pref.setButton2OnClickListener(any(View.OnClickListener.class))).thenReturn(pref); + + when(pref.setButton3Text(anyInt())).thenReturn(pref); + when(pref.setButton3Icon(anyInt())).thenReturn(pref); + when(pref.setButton3Enabled(anyBoolean())).thenReturn(pref); + when(pref.setButton3Visible(anyBoolean())).thenReturn(pref); + when(pref.setButton3OnClickListener(any(View.OnClickListener.class))).thenReturn(pref); + + when(pref.setButton4Text(anyInt())).thenReturn(pref); + when(pref.setButton4Icon(anyInt())).thenReturn(pref); + when(pref.setButton4Enabled(anyBoolean())).thenReturn(pref); + when(pref.setButton4Visible(anyBoolean())).thenReturn(pref); + when(pref.setButton4OnClickListener(any(View.OnClickListener.class))).thenReturn(pref); + + return pref; + } +} diff --git a/tests/robotests/src/com/android/settings/wifi/details2/WifiMeteredPreferenceController2Test.java b/tests/robotests/src/com/android/settings/wifi/details2/WifiMeteredPreferenceController2Test.java new file mode 100644 index 00000000000..517c96a3159 --- /dev/null +++ b/tests/robotests/src/com/android/settings/wifi/details2/WifiMeteredPreferenceController2Test.java @@ -0,0 +1,96 @@ +/* + * 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.details2; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.net.wifi.WifiConfiguration; + +import androidx.preference.DropDownPreference; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class WifiMeteredPreferenceController2Test { + + private static final int METERED_OVERRIDE_NONE = 0; + private static final int METERED_OVERRIDE_METERED = 1; + private static final int METERED_OVERRIDE_NOT_METERED = 2; + + @Mock + private WifiConfiguration mWifiConfiguration; + + private WifiMeteredPreferenceController2 mPreferenceController; + private Context mContext; + private DropDownPreference mDropDownPreference; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + + mPreferenceController = spy( + new WifiMeteredPreferenceController2(mContext, mWifiConfiguration)); + mDropDownPreference = new DropDownPreference(mContext); + mDropDownPreference.setEntries(R.array.wifi_metered_entries); + mDropDownPreference.setEntryValues(R.array.wifi_metered_values); + } + + @Test + public void testUpdateState_wifiMetered_setCorrectValue() { + doReturn(METERED_OVERRIDE_METERED).when(mPreferenceController).getMeteredOverride(); + + mPreferenceController.updateState(mDropDownPreference); + + assertThat(mDropDownPreference.getEntry()).isEqualTo("Treat as metered"); + } + + @Test + public void testUpdateState_wifiNotMetered_setCorrectValue() { + doReturn(METERED_OVERRIDE_NOT_METERED).when(mPreferenceController).getMeteredOverride(); + + mPreferenceController.updateState(mDropDownPreference); + + assertThat(mDropDownPreference.getEntry()).isEqualTo("Treat as unmetered"); + } + + @Test + public void testUpdateState_wifiAuto_setCorrectValue() { + doReturn(METERED_OVERRIDE_NONE).when(mPreferenceController).getMeteredOverride(); + + mPreferenceController.updateState(mDropDownPreference); + + assertThat(mDropDownPreference.getEntry()).isEqualTo("Detect automatically"); + } + + @Test + public void testController_resilientToNullConfig() { + mPreferenceController = spy(new WifiMeteredPreferenceController2(mContext, null)); + + mPreferenceController.getMeteredOverride(); + mPreferenceController.onPreferenceChange(mDropDownPreference, 1); + } +} diff --git a/tests/robotests/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController2Test.java b/tests/robotests/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController2Test.java new file mode 100644 index 00000000000..91cc01e3500 --- /dev/null +++ b/tests/robotests/src/com/android/settings/wifi/details2/WifiPrivacyPreferenceController2Test.java @@ -0,0 +1,128 @@ +/* + * 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.details2; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import android.content.Context; +import android.net.wifi.WifiConfiguration; + +import androidx.preference.DropDownPreference; + +import com.android.settings.R; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +@RunWith(RobolectricTestRunner.class) +public class WifiPrivacyPreferenceController2Test { + + private static final int PRIVACY_RANDOMIZED = WifiConfiguration.RANDOMIZATION_PERSISTENT; + private static final int PRIVACY_TRUSTED = WifiConfiguration.RANDOMIZATION_NONE; + + @Mock + private WifiConfiguration mWifiConfiguration; + + private WifiPrivacyPreferenceController2 mPreferenceController; + private Context mContext; + private DropDownPreference mDropDownPreference; + private String[] mPerferenceStrings; + + @Before + public void setUp() { + mContext = RuntimeEnvironment.application; + + WifiPrivacyPreferenceController2 preferenceController = + new WifiPrivacyPreferenceController2(mContext); + preferenceController.setWifiConfiguration(mWifiConfiguration); + mPreferenceController = spy(preferenceController); + mDropDownPreference = new DropDownPreference(mContext); + mDropDownPreference.setEntries(R.array.wifi_privacy_entries); + mDropDownPreference.setEntryValues(R.array.wifi_privacy_values); + + mPerferenceStrings = mContext.getResources().getStringArray(R.array.wifi_privacy_entries); + } + + @Test + public void testUpdateState_wifiPrivacy_setCorrectValue() { + doReturn(PRIVACY_TRUSTED).when(mPreferenceController).getRandomizationValue(); + + mPreferenceController.updateState(mDropDownPreference); + + int prefValue = mPreferenceController.translateMacRandomizedValueToPrefValue( + PRIVACY_TRUSTED); + assertThat(mDropDownPreference.getEntry()).isEqualTo(mPerferenceStrings[prefValue]); + } + + @Test + public void testUpdateState_wifiNotMetered_setCorrectValue() { + doReturn(PRIVACY_RANDOMIZED).when(mPreferenceController).getRandomizationValue(); + + mPreferenceController.updateState(mDropDownPreference); + + int prefValue = mPreferenceController.translateMacRandomizedValueToPrefValue( + PRIVACY_RANDOMIZED); + assertThat(mDropDownPreference.getEntry()).isEqualTo(mPerferenceStrings[prefValue]); + } + + @Test + public void testController_resilientToNullConfig() { + mPreferenceController = spy(new WifiPrivacyPreferenceController2(mContext)); + + mPreferenceController.getRandomizationValue(); + mPreferenceController.onPreferenceChange(mDropDownPreference, "1"); + } + + @Test + public void testUpdateState_isNotEphemeralNetwork_shouldBeSelectable() { + mPreferenceController.setIsEphemeral(false); + mPreferenceController.updateState(mDropDownPreference); + + assertThat(mDropDownPreference.isSelectable()).isTrue(); + } + + @Test + public void testUpdateState_isEphemeralNetwork_shouldNotSelectable() { + mPreferenceController.setIsEphemeral(true); + mPreferenceController.updateState(mDropDownPreference); + + assertThat(mDropDownPreference.isSelectable()).isFalse(); + } + + @Test + public void testUpdateState_isNotPasspointNetwork_shouldBeSelectable() { + mPreferenceController.setIsPasspoint(false); + mPreferenceController.updateState(mDropDownPreference); + + assertThat(mDropDownPreference.isSelectable()).isTrue(); + } + + @Test + public void testUpdateState_isPasspointNetwork_shouldNotSelectable() { + mPreferenceController.setIsPasspoint(true); + mPreferenceController.updateState(mDropDownPreference); + + assertThat(mDropDownPreference.isSelectable()).isFalse(); + } +} diff --git a/tests/robotests/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsPreferenceController2Test.java b/tests/robotests/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsPreferenceController2Test.java new file mode 100644 index 00000000000..6be9852be90 --- /dev/null +++ b/tests/robotests/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsPreferenceController2Test.java @@ -0,0 +1,128 @@ +/* + * 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.savedaccesspoints2; + +import static com.android.settings.core.BasePreferenceController.AVAILABLE; +import static com.android.settings.core.BasePreferenceController.CONDITIONALLY_UNAVAILABLE; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; +import android.os.Bundle; + +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; + +import com.android.settings.testutils.shadow.ShadowAccessPoint; +import com.android.settings.testutils.shadow.ShadowWifiManager; +import com.android.settingslib.wifi.AccessPoint; +import com.android.settingslib.wifi.AccessPointPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +import java.util.ArrayList; +import java.util.Arrays; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowWifiManager.class}) +public class SavedAccessPointsPreferenceController2Test { + + @Mock + private PreferenceScreen mPreferenceScreen; + @Mock + private PreferenceCategory mPreferenceCategory; + + private Context mContext; + private WifiManager mWifiManager; + private SavedAccessPointsWifiSettings2 mSettings; + private SavedAccessPointsPreferenceController2 mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mWifiManager = mContext.getSystemService(WifiManager.class); + mSettings = spy(new SavedAccessPointsWifiSettings2()); + mController = spy(new SavedAccessPointsPreferenceController2(mContext, "test_key")); + mController.setHost(mSettings); + + when(mPreferenceScreen.findPreference(mController.getPreferenceKey())) + .thenReturn(mPreferenceCategory); + when(mPreferenceCategory.getContext()).thenReturn(mContext); + } + + @Test + public void getAvailability_noSavedAccessPoint_shouldNotAvailable() { + mController.mAccessPoints = new ArrayList<>(); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(CONDITIONALLY_UNAVAILABLE); + } + + @Test + public void getAvailability_oneSavedAccessPoint_shouldAvailable() { + final AccessPoint accessPoint = new AccessPoint(mContext, new Bundle() /* savedState */); + mController.mAccessPoints = new ArrayList(Arrays.asList(accessPoint)); + + assertThat(mController.getAvailabilityStatus()).isEqualTo(AVAILABLE); + } + + @Test + @Config(shadows = ShadowAccessPoint.class) + public void displayPreference_oneAccessPoint_shouldListNonSubscribedAPs() { + final WifiConfiguration config = new WifiConfiguration(); + config.SSID = "SSID"; + config.BSSID = "BSSID"; + config.networkId = 2; + mWifiManager.addNetwork(config); + + final ArgumentCaptor captor = + ArgumentCaptor.forClass(AccessPointPreference.class); + mController.displayPreference(mPreferenceScreen); + + verify(mPreferenceCategory).addPreference(captor.capture()); + + final AccessPointPreference pref = captor.getValue(); + assertThat(pref.getTitle()).isEqualTo(config.SSID); + } + + @Test + @Config(shadows = ShadowAccessPoint.class) + public void displayPreference_onePasspoint_shouldNotListSubscribedAPs() { + mWifiManager.addOrUpdatePasspointConfiguration( + SubscribedAccessPointsPreferenceController2Test.createMockPasspointConfiguration()); + + mController.displayPreference(mPreferenceScreen); + + verify(mPreferenceCategory, never()).addPreference(any(AccessPointPreference.class)); + } +} diff --git a/tests/robotests/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsWifiSettings2Test.java b/tests/robotests/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsWifiSettings2Test.java new file mode 100644 index 00000000000..ab1b51c2bcf --- /dev/null +++ b/tests/robotests/src/com/android/settings/wifi/savedaccesspoints2/SavedAccessPointsWifiSettings2Test.java @@ -0,0 +1,69 @@ +/* + * 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.savedaccesspoints2; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; + +import com.android.internal.logging.nano.MetricsProto.MetricsEvent; +import com.android.settings.R; +import com.android.settingslib.core.AbstractPreferenceController; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; + +@RunWith(RobolectricTestRunner.class) +public class SavedAccessPointsWifiSettings2Test { + + @Mock + private SubscribedAccessPointsPreferenceController2 mSubscribedApController; + @Mock + private SavedAccessPointsPreferenceController2 mSavedApController; + + private TestFragment mSettings; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mSettings = spy(new TestFragment()); + + doReturn(mSubscribedApController).when(mSettings) + .use(SubscribedAccessPointsPreferenceController2.class); + doReturn(mSavedApController).when(mSettings) + .use(SavedAccessPointsPreferenceController2.class); + } + + @Test + public void verifyConstants() { + assertThat(mSettings.getMetricsCategory()).isEqualTo(MetricsEvent.WIFI_SAVED_ACCESS_POINTS); + assertThat(mSettings.getPreferenceScreenResId()) + .isEqualTo(R.xml.wifi_display_saved_access_points2); + } + + public static class TestFragment extends SavedAccessPointsWifiSettings2 { + + public T use(Class clazz) { + return super.use(clazz); + } + } +} diff --git a/tests/robotests/src/com/android/settings/wifi/savedaccesspoints2/SubscribedAccessPointsPreferenceController2Test.java b/tests/robotests/src/com/android/settings/wifi/savedaccesspoints2/SubscribedAccessPointsPreferenceController2Test.java new file mode 100644 index 00000000000..2ce5d441c91 --- /dev/null +++ b/tests/robotests/src/com/android/settings/wifi/savedaccesspoints2/SubscribedAccessPointsPreferenceController2Test.java @@ -0,0 +1,115 @@ +/* + * 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.savedaccesspoints2; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; +import android.net.wifi.hotspot2.PasspointConfiguration; +import android.net.wifi.hotspot2.pps.HomeSp; + +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; + +import com.android.settings.testutils.shadow.ShadowAccessPoint; +import com.android.settings.testutils.shadow.ShadowWifiManager; +import com.android.settingslib.wifi.AccessPointPreference; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(shadows = {ShadowWifiManager.class}) +public class SubscribedAccessPointsPreferenceController2Test { + + @Mock + private PreferenceScreen mPreferenceScreen; + @Mock + private PreferenceCategory mPreferenceCategory; + + private Context mContext; + private WifiManager mWifiManager; + private SavedAccessPointsWifiSettings2 mSettings; + private SubscribedAccessPointsPreferenceController2 mController; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = RuntimeEnvironment.application; + mWifiManager = mContext.getSystemService(WifiManager.class); + mSettings = spy(new SavedAccessPointsWifiSettings2()); + mController = spy(new SubscribedAccessPointsPreferenceController2(mContext, "test_key")); + mController.setHost(mSettings); + + when(mPreferenceScreen.findPreference(mController.getPreferenceKey())) + .thenReturn(mPreferenceCategory); + when(mPreferenceCategory.getContext()).thenReturn(mContext); + } + + @Test + @Config(shadows = ShadowAccessPoint.class) + public void displayPreference_oneAccessPoint_shouldNotListNonSubscribedAPs() { + final WifiConfiguration config = new WifiConfiguration(); + config.SSID = "SSID"; + config.BSSID = "BSSID"; + config.networkId = 2; + mWifiManager.addNetwork(config); + + mController.displayPreference(mPreferenceScreen); + + verify(mPreferenceCategory, never()).addPreference(any(AccessPointPreference.class)); + } + + @Test + @Config(shadows = ShadowAccessPoint.class) + public void displayPreference_onePasspoint_shouldListSubscribedAPs() { + mWifiManager.addOrUpdatePasspointConfiguration(createMockPasspointConfiguration()); + + mController.displayPreference(mPreferenceScreen); + + final ArgumentCaptor captor = + ArgumentCaptor.forClass(AccessPointPreference.class); + verify(mPreferenceCategory).addPreference(captor.capture()); + + final AccessPointPreference pref = captor.getValue(); + assertThat(pref.getTitle()).isEqualTo("TESTPASSPOINT"); + } + + public static PasspointConfiguration createMockPasspointConfiguration() { + final PasspointConfiguration config = new PasspointConfiguration(); + final HomeSp homeSp = new HomeSp(); + homeSp.setFqdn("FQDN"); + homeSp.setFriendlyName("TESTPASSPOINT"); + config.setHomeSp(homeSp); + return config; + } +}