From 746176eb5602f4192a36aaee2f0759acb764899a Mon Sep 17 00:00:00 2001 From: Arc Wang Date: Wed, 2 Sep 2020 17:35:56 +0800 Subject: [PATCH] Create NetworkProviderSettings for provider model Provider model is a feature which improves networks Settings UX. NetworkProviderSettings is the fragment which allow users to choose a mobile network or a Wi-Fi network to connect. At this change, NetworkProviderSettings is a clone of WifiSettings, mobile networks will be integrated at later CLs. Bug: 167474581 Test: make RunSettingsRoboTests ROBOTEST_FILTER=com.android.settings.network.NetworkProviderSettingsTest Change-Id: I185639a8f2469e9ec76ad18b6c2bc2e8a4d079e3 --- AndroidManifest.xml | 20 + res/xml/network_provider_settings.xml | 48 + src/com/android/settings/Settings.java | 1 + .../core/gateway/SettingsGateway.java | 5 +- .../network/NetworkProviderSettings.java | 1149 +++++++++++++++++ .../settings/wifi/AddNetworkFragment.java | 2 +- .../android/settings/wifi/WifiSettings.java | 14 + .../network/NetworkProviderSettingsTest.java | 377 ++++++ 8 files changed, 1614 insertions(+), 2 deletions(-) create mode 100644 res/xml/network_provider_settings.xml create mode 100644 src/com/android/settings/network/NetworkProviderSettings.java create mode 100644 tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 66f72075661..19ef8945861 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -279,6 +279,26 @@ android:value="true" /> + + + + + + + + + + + + + diff --git a/res/xml/network_provider_settings.xml b/res/xml/network_provider_settings.xml new file mode 100644 index 00000000000..778def0ca48 --- /dev/null +++ b/res/xml/network_provider_settings.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + diff --git a/src/com/android/settings/Settings.java b/src/com/android/settings/Settings.java index 2f23c849280..026853f3af7 100644 --- a/src/com/android/settings/Settings.java +++ b/src/com/android/settings/Settings.java @@ -82,6 +82,7 @@ public class Settings extends SettingsActivity { public static class PublicVolumeSettingsActivity extends SettingsActivity { /* empty */ } public static class WifiSettingsActivity extends SettingsActivity { /* empty */ } public static class WifiSettings2Activity extends SettingsActivity { /* empty */ } + public static class NetworkProviderSettingsActivity extends SettingsActivity { /* empty */ } public static class WifiP2pSettingsActivity extends SettingsActivity { /* empty */ } public static class AvailableVirtualKeyboardActivity extends SettingsActivity { /* empty */ } public static class KeyboardLayoutPickerActivity extends SettingsActivity { /* empty */ } diff --git a/src/com/android/settings/core/gateway/SettingsGateway.java b/src/com/android/settings/core/gateway/SettingsGateway.java index 6c6bc90bbbe..4a5d71a055d 100644 --- a/src/com/android/settings/core/gateway/SettingsGateway.java +++ b/src/com/android/settings/core/gateway/SettingsGateway.java @@ -112,6 +112,7 @@ import com.android.settings.network.ApnEditor; import com.android.settings.network.ApnSettings; import com.android.settings.network.MobileNetworkListFragment; import com.android.settings.network.NetworkDashboardFragment; +import com.android.settings.network.NetworkProviderSettings; import com.android.settings.nfc.AndroidBeam; import com.android.settings.nfc.PaymentSettings; import com.android.settings.notification.ConfigureNotificationSettings; @@ -303,7 +304,8 @@ public class SettingsGateway { GestureNavigationSettingsFragment.class.getName(), InteractAcrossProfilesSettings.class.getName(), InteractAcrossProfilesDetails.class.getName(), - MediaControlsSettings.class.getName() + MediaControlsSettings.class.getName(), + NetworkProviderSettings.class.getName() }; public static final String[] SETTINGS_FOR_RESTRICTED = { @@ -324,6 +326,7 @@ public class SettingsGateway { // Home page > Network & Internet Settings.WifiSettingsActivity.class.getName(), Settings.DataUsageSummaryActivity.class.getName(), + Settings.NetworkProviderSettingsActivity.class.getName(), // Home page > Connected devices Settings.BluetoothSettingsActivity.class.getName(), Settings.WifiDisplaySettingsActivity.class.getName(), diff --git a/src/com/android/settings/network/NetworkProviderSettings.java b/src/com/android/settings/network/NetworkProviderSettings.java new file mode 100644 index 00000000000..f6f19b5bfba --- /dev/null +++ b/src/com/android/settings/network/NetworkProviderSettings.java @@ -0,0 +1,1149 @@ +/* + * Copyright (C) 2020 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.network; + +import static android.net.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED; +import static android.os.UserManager.DISALLOW_CONFIG_WIFI; + +import android.app.Activity; +import android.app.Dialog; +import android.app.settings.SettingsEnums; +import android.content.ActivityNotFoundException; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.net.ConnectivityManager; +import android.net.NetworkScoreManager; +import android.net.NetworkTemplate; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.PowerManager; +import android.os.Process; +import android.os.SimpleClock; +import android.os.SystemClock; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.FeatureFlagUtils; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Toast; + +import androidx.annotation.VisibleForTesting; +import androidx.preference.Preference; +import androidx.preference.PreferenceCategory; +import androidx.preference.PreferenceScreen; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.settings.LinkifyUtils; +import com.android.settings.R; +import com.android.settings.RestrictedSettingsFragment; +import com.android.settings.SettingsActivity; +import com.android.settings.core.FeatureFlags; +import com.android.settings.core.SubSettingLauncher; +import com.android.settings.datausage.DataUsagePreference; +import com.android.settings.datausage.DataUsageUtils; +import com.android.settings.location.ScanningSettings; +import com.android.settings.search.BaseSearchIndexProvider; +import com.android.settings.widget.SwitchBarController; +import com.android.settings.wifi.AddNetworkFragment; +import com.android.settings.wifi.AddWifiNetworkPreference; +import com.android.settings.wifi.ConfigureWifiEntryFragment; +import com.android.settings.wifi.ConnectedWifiEntryPreference; +import com.android.settings.wifi.LinkablePreference; +import com.android.settings.wifi.WifiConfigUiBase2; +import com.android.settings.wifi.WifiConnectListener; +import com.android.settings.wifi.WifiDialog2; +import com.android.settings.wifi.WifiEnabler; +import com.android.settings.wifi.WifiUtils; +import com.android.settings.wifi.details2.WifiNetworkDetailsFragment2; +import com.android.settings.wifi.dpp.WifiDppUtils; +import com.android.settingslib.HelpUtils; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedLockUtilsInternal; +import com.android.settingslib.search.Indexable; +import com.android.settingslib.search.SearchIndexable; +import com.android.settingslib.wifi.LongPressWifiEntryPreference; +import com.android.settingslib.wifi.WifiSavedConfigUtils; +import com.android.wifitrackerlib.WifiEntry; +import com.android.wifitrackerlib.WifiEntry.ConnectCallback; +import com.android.wifitrackerlib.WifiPickerTracker; + +import java.time.Clock; +import java.time.ZoneOffset; +import java.util.List; +import java.util.Optional; + +/** + * UI for Mobile network and Wi-Fi network settings. + * + * TODO(b/167474581): Define the intent android.settings.NETWORK_PROVIDER_SETTINGS in Settings.java. + */ +@SearchIndexable +public class NetworkProviderSettings extends RestrictedSettingsFragment + implements Indexable, WifiPickerTracker.WifiPickerTrackerCallback, + WifiDialog2.WifiDialog2Listener, DialogInterface.OnDismissListener { + + private static final String TAG = "NetworkProviderSettings"; + + // IDs of context menu + static final int MENU_ID_CONNECT = Menu.FIRST + 1; + @VisibleForTesting + static final int MENU_ID_DISCONNECT = Menu.FIRST + 2; + @VisibleForTesting + static final int MENU_ID_FORGET = Menu.FIRST + 3; + static final int MENU_ID_MODIFY = Menu.FIRST + 4; + + // Max age of tracked WifiEntries + private static final long MAX_SCAN_AGE_MILLIS = 15_000; + // Interval between initiating WifiPickerTracker scans + private static final long SCAN_INTERVAL_MILLIS = 10_000; + + @VisibleForTesting + static final int ADD_NETWORK_REQUEST = 2; + static final int CONFIG_NETWORK_REQUEST = 3; + static final int MANAGE_SUBSCRIPTION = 4; + + private static final String PREF_KEY_EMPTY_WIFI_LIST = "wifi_empty_list"; + // TODO(b/70983952): Rename these to use WifiEntry instead of AccessPoint. + private static final String PREF_KEY_CONNECTED_ACCESS_POINTS = "connected_access_point"; + private static final String PREF_KEY_ACCESS_POINTS = "access_points"; + private static final String PREF_KEY_CONFIGURE_WIFI_SETTINGS = "configure_wifi_settings"; + private static final String PREF_KEY_SAVED_NETWORKS = "saved_networks"; + private static final String PREF_KEY_STATUS_MESSAGE = "wifi_status_message"; + @VisibleForTesting + static final String PREF_KEY_DATA_USAGE = "wifi_data_usage"; + + private static final int REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER = 0; + + public static final int WIFI_DIALOG_ID = 1; + + // Instance state keys + private static final String SAVE_DIALOG_MODE = "dialog_mode"; + private static final String SAVE_DIALOG_WIFIENTRY_KEY = "wifi_ap_key"; + + // Cache at onCreateContextMenu and use at onContextItemSelected. Don't use it in other methods. + private WifiEntry mSelectedWifiEntry; + + // Save the dialog details + private int mDialogMode; + private String mDialogWifiEntryKey; + private WifiEntry mDialogWifiEntry; + + // This boolean extra specifies whether to enable the Next button when connected. Used by + // account creation outside of setup wizard. + private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect"; + + // Enable the Next button when a Wi-Fi network is connected. + private boolean mEnableNextOnConnection; + + // This string extra specifies a network to open the connect dialog on, so the user can enter + // network credentials. This is used by quick settings for secured networks, among other + // things. + private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid"; + private String mOpenSsid; + + private static boolean isVerboseLoggingEnabled() { + return WifiPickerTracker.isVerboseLoggingEnabled(); + } + + private final Runnable mUpdateWifiEntryPreferencesRunnable = () -> { + updateWifiEntryPreferences(); + }; + private final Runnable mHideProgressBarRunnable = () -> { + setProgressBarVisible(false); + }; + + protected WifiManager mWifiManager; + private WifiManager.ActionListener mConnectListener; + private WifiManager.ActionListener mSaveListener; + private WifiManager.ActionListener mForgetListener; + + /** + * The state of {@link #isUiRestricted()} at {@link #onCreate(Bundle)}}. This is necessary to + * ensure that behavior is consistent if {@link #isUiRestricted()} changes. It could be changed + * by the Test DPC tool in AFW mode. + */ + private boolean mIsRestricted; + + private WifiEnabler mWifiEnabler; + + // Worker thread used for WifiPickerTracker work + private HandlerThread mWorkerThread; + + @VisibleForTesting + WifiPickerTracker mWifiPickerTracker; + + private WifiDialog2 mDialog; + + private View mProgressHeader; + + private PreferenceCategory mConnectedWifiEntryPreferenceCategory; + private PreferenceCategory mWifiEntryPreferenceCategory; + @VisibleForTesting + AddWifiNetworkPreference mAddWifiNetworkPreference; + @VisibleForTesting + Preference mConfigureWifiSettingsPreference; + @VisibleForTesting + Preference mSavedNetworksPreference; + @VisibleForTesting + DataUsagePreference mDataUsagePreference; + private LinkablePreference mStatusMessagePreference; + + /** + * Tracks whether the user initiated a connection via clicking in order to autoscroll to the + * network once connected. + */ + private boolean mClickedConnect; + + public NetworkProviderSettings() { + super(DISALLOW_CONFIG_WIFI); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + final Activity activity = getActivity(); + if (activity != null) { + mProgressHeader = setPinnedHeaderView(R.layout.progress_header) + .findViewById(R.id.progress_bar_animation); + setProgressBarVisible(false); + + ((SettingsActivity) activity).getSwitchBar().setSwitchBarText( + R.string.wifi_settings_primary_switch_title, + R.string.wifi_settings_primary_switch_title); + } + } + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + // TODO(b/37429702): Add animations and preference comparator back after initial screen is + // loaded (ODR). + setAnimationAllowed(false); + + addPreferences(); + + mIsRestricted = isUiRestricted(); + } + + private void addPreferences() { + addPreferencesFromResource(R.xml.network_provider_settings); + + mConnectedWifiEntryPreferenceCategory = findPreference(PREF_KEY_CONNECTED_ACCESS_POINTS); + mWifiEntryPreferenceCategory = findPreference(PREF_KEY_ACCESS_POINTS); + mConfigureWifiSettingsPreference = findPreference(PREF_KEY_CONFIGURE_WIFI_SETTINGS); + mSavedNetworksPreference = findPreference(PREF_KEY_SAVED_NETWORKS); + mAddWifiNetworkPreference = new AddWifiNetworkPreference(getPrefContext()); + mStatusMessagePreference = findPreference(PREF_KEY_STATUS_MESSAGE); + mDataUsagePreference = findPreference(PREF_KEY_DATA_USAGE); + mDataUsagePreference.setVisible(DataUsageUtils.hasWifiRadio(getContext())); + mDataUsagePreference.setTemplate(NetworkTemplate.buildTemplateWifiWildcard(), + 0 /*subId*/, + null /*service*/); + } + + @Override + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + final Context context = getContext(); + mWorkerThread = new HandlerThread(TAG + + "{" + Integer.toHexString(System.identityHashCode(this)) + "}", + Process.THREAD_PRIORITY_BACKGROUND); + mWorkerThread.start(); + final Clock elapsedRealtimeClock = new SimpleClock(ZoneOffset.UTC) { + @Override + public long millis() { + return SystemClock.elapsedRealtime(); + } + }; + mWifiPickerTracker = new WifiPickerTracker(getSettingsLifecycle(), context, + context.getSystemService(WifiManager.class), + context.getSystemService(ConnectivityManager.class), + context.getSystemService(NetworkScoreManager.class), + new Handler(Looper.getMainLooper()), + mWorkerThread.getThreadHandler(), + elapsedRealtimeClock, + MAX_SCAN_AGE_MILLIS, + SCAN_INTERVAL_MILLIS, + this); + + final Activity activity = getActivity(); + + if (activity != null) { + mWifiManager = getActivity().getSystemService(WifiManager.class); + } + + mConnectListener = new WifiConnectListener(getActivity()); + + mSaveListener = new WifiManager.ActionListener() { + @Override + public void onSuccess() { + } + + @Override + public void onFailure(int reason) { + Activity activity = getActivity(); + if (activity != null) { + Toast.makeText(activity, + R.string.wifi_failed_save_message, + Toast.LENGTH_SHORT).show(); + } + } + }; + + mForgetListener = new WifiManager.ActionListener() { + @Override + public void onSuccess() { + } + + @Override + public void onFailure(int reason) { + Activity activity = getActivity(); + if (activity != null) { + Toast.makeText(activity, + R.string.wifi_failed_forget_message, + Toast.LENGTH_SHORT).show(); + } + } + }; + registerForContextMenu(getListView()); + setHasOptionsMenu(true); + + if (savedInstanceState != null) { + mDialogMode = savedInstanceState.getInt(SAVE_DIALOG_MODE); + mDialogWifiEntryKey = savedInstanceState.getString(SAVE_DIALOG_WIFIENTRY_KEY); + } + + // If we're supposed to enable/disable the Next button based on our current connection + // state, start it off in the right state. + final Intent intent = getActivity().getIntent(); + mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false); + + if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) { + mOpenSsid = intent.getStringExtra(EXTRA_START_CONNECT_SSID); + } + } + + @Override + public void onDestroyView() { + if (mWifiEnabler != null) { + mWifiEnabler.teardownSwitchController(); + } + mWorkerThread.quit(); + + super.onDestroyView(); + } + + @Override + public void onStart() { + super.onStart(); + + mWifiEnabler = createWifiEnabler(); + + if (mIsRestricted) { + restrictUi(); + } + } + + private void restrictUi() { + if (!isUiRestrictedByOnlyAdmin()) { + getEmptyTextView().setText(R.string.wifi_empty_list_user_restricted); + } + getPreferenceScreen().removeAll(); + } + + /** + * @return new WifiEnabler + */ + private WifiEnabler createWifiEnabler() { + final SettingsActivity activity = (SettingsActivity) getActivity(); + return new WifiEnabler(activity, new SwitchBarController(activity.getSwitchBar()), + mMetricsFeatureProvider); + } + + @Override + public void onResume() { + final Activity activity = getActivity(); + super.onResume(); + + // Because RestrictedSettingsFragment's onResume potentially requests authorization, + // which changes the restriction state, recalculate it. + final boolean alreadyImmutablyRestricted = mIsRestricted; + mIsRestricted = isUiRestricted(); + if (!alreadyImmutablyRestricted && mIsRestricted) { + restrictUi(); + } + + if (mWifiEnabler != null) { + mWifiEnabler.resume(activity); + } + + changeNextButtonState(mWifiPickerTracker.getConnectedWifiEntry() != null); + } + + @Override + public void onPause() { + super.onPause(); + if (mWifiEnabler != null) { + mWifiEnabler.pause(); + } + } + + @Override + public void onStop() { + getView().removeCallbacks(mUpdateWifiEntryPreferencesRunnable); + getView().removeCallbacks(mHideProgressBarRunnable); + super.onStop(); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == ADD_NETWORK_REQUEST) { + handleAddNetworkRequest(resultCode, data); + return; + } else if (requestCode == REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER) { + if (resultCode == Activity.RESULT_OK) { + if (mDialog != null) { + mDialog.dismiss(); + } + } + return; + } else if (requestCode == CONFIG_NETWORK_REQUEST) { + if (resultCode == Activity.RESULT_OK) { + final WifiConfiguration wifiConfiguration = data.getParcelableExtra( + ConfigureWifiEntryFragment.NETWORK_CONFIG_KEY); + if (wifiConfiguration != null) { + mWifiManager.connect(wifiConfiguration, + new WifiConnectActionListener()); + } + } + return; + } else if (requestCode == MANAGE_SUBSCRIPTION) { + //Do nothing + return; + } + + final boolean formerlyRestricted = mIsRestricted; + mIsRestricted = isUiRestricted(); + if (formerlyRestricted && !mIsRestricted + && getPreferenceScreen().getPreferenceCount() == 0) { + // De-restrict the ui + addPreferences(); + } + } + + @Override + protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) { + final RecyclerView.Adapter adapter = super.onCreateAdapter(preferenceScreen); + adapter.setHasStableIds(true); + return adapter; + } + + @Override + public int getMetricsCategory() { + return SettingsEnums.WIFI; + } + + @Override + public void onSaveInstanceState(Bundle outState) { + super.onSaveInstanceState(outState); + // If dialog has been shown, save its state. + if (mDialog != null) { + outState.putInt(SAVE_DIALOG_MODE, mDialogMode); + outState.putString(SAVE_DIALOG_WIFIENTRY_KEY, mDialogWifiEntryKey); + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) { + Preference preference = (Preference) view.getTag(); + if (!(preference instanceof LongPressWifiEntryPreference)) { + // Do nothing. + return; + } + + // Cache the WifiEntry for onContextItemSelected. Don't use it in other methods. + mSelectedWifiEntry = ((LongPressWifiEntryPreference) preference).getWifiEntry(); + + menu.setHeaderTitle(mSelectedWifiEntry.getTitle()); + if (mSelectedWifiEntry.canConnect()) { + menu.add(Menu.NONE, MENU_ID_CONNECT, 0 /* order */, R.string.wifi_connect); + } + + if (mSelectedWifiEntry.canDisconnect()) { + menu.add(Menu.NONE, MENU_ID_DISCONNECT, 0 /* order */, + R.string.wifi_disconnect_button_text); + } + + // "forget" for normal saved network. And "disconnect" for ephemeral network because it + // could only be disconnected and be put in blocklists so it won't be used again. + if (canForgetNetwork()) { + menu.add(Menu.NONE, MENU_ID_FORGET, 0 /* order */, R.string.forget); + } + + WifiConfiguration config = mSelectedWifiEntry.getWifiConfiguration(); + // Some configs are ineditable + if (WifiUtils.isNetworkLockedDown(getActivity(), config)) { + return; + } + + if (mSelectedWifiEntry.isSaved() && mSelectedWifiEntry.getConnectedState() + != WifiEntry.CONNECTED_STATE_CONNECTED) { + menu.add(Menu.NONE, MENU_ID_MODIFY, 0 /* order */, R.string.wifi_modify); + } + } + + private boolean canForgetNetwork() { + return mSelectedWifiEntry.canForget() && !WifiUtils.isNetworkLockedDown(getActivity(), + mSelectedWifiEntry.getWifiConfiguration()); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case MENU_ID_CONNECT: + connect(mSelectedWifiEntry, true /* editIfNoConfig */, false /* fullScreenEdit */); + return true; + case MENU_ID_DISCONNECT: + mSelectedWifiEntry.disconnect(null /* callback */); + return true; + case MENU_ID_FORGET: + forget(mSelectedWifiEntry); + return true; + case MENU_ID_MODIFY: + showDialog(mSelectedWifiEntry, WifiConfigUiBase2.MODE_MODIFY); + return true; + default: + return super.onContextItemSelected(item); + } + } + + @Override + public boolean onPreferenceTreeClick(Preference preference) { + // If the preference has a fragment set, open that + if (preference.getFragment() != null) { + preference.setOnPreferenceClickListener(null); + return super.onPreferenceTreeClick(preference); + } + + if (preference instanceof LongPressWifiEntryPreference) { + final WifiEntry selectedEntry = + ((LongPressWifiEntryPreference) preference).getWifiEntry(); + + if (selectedEntry.shouldEditBeforeConnect()) { + launchConfigNewNetworkFragment(selectedEntry); + return true; + } + + connect(selectedEntry, true /* editIfNoConfig */, true /* fullScreenEdit */); + } else if (preference == mAddWifiNetworkPreference) { + onAddNetworkPressed(); + } else { + return super.onPreferenceTreeClick(preference); + } + return true; + } + + private void showDialog(WifiEntry wifiEntry, int dialogMode) { + if (WifiUtils.isNetworkLockedDown(getActivity(), wifiEntry.getWifiConfiguration()) + && wifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED) { + RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(), + RestrictedLockUtilsInternal.getDeviceOwner(getActivity())); + return; + } + + if (mDialog != null) { + removeDialog(WIFI_DIALOG_ID); + mDialog = null; + } + + // Save the access point and edit mode + mDialogWifiEntry = wifiEntry; + mDialogWifiEntryKey = wifiEntry.getKey(); + mDialogMode = dialogMode; + + showDialog(WIFI_DIALOG_ID); + } + + @Override + public Dialog onCreateDialog(int dialogId) { + switch (dialogId) { + case WIFI_DIALOG_ID: + // modify network + mDialog = WifiDialog2 + .createModal(getActivity(), this, mDialogWifiEntry, mDialogMode); + return mDialog; + default: + return super.onCreateDialog(dialogId); + } + } + + @Override + public void onDialogShowing() { + super.onDialogShowing(); + setOnDismissListener(this); + } + + @Override + public void onDismiss(DialogInterface dialog) { + // We don't keep any dialog object when dialog was dismissed. + mDialog = null; + mDialogWifiEntry = null; + mDialogWifiEntryKey = null; + } + + @Override + public int getDialogMetricsCategory(int dialogId) { + switch (dialogId) { + case WIFI_DIALOG_ID: + return SettingsEnums.DIALOG_WIFI_AP_EDIT; + default: + return 0; + } + } + + /** Called when the state of Wifi has changed. */ + @Override + public void onWifiStateChanged() { + if (mIsRestricted) { + return; + } + final int wifiState = mWifiPickerTracker.getWifiState(); + + if (isVerboseLoggingEnabled()) { + Log.i(TAG, "onWifiStateChanged called with wifi state: " + wifiState); + } + + switch (wifiState) { + case WifiManager.WIFI_STATE_ENABLED: + updateWifiEntryPreferences(); + break; + + case WifiManager.WIFI_STATE_ENABLING: + removeConnectedWifiEntryPreference(); + removeWifiEntryPreference(); + addMessagePreference(R.string.wifi_starting); + setProgressBarVisible(true); + break; + + case WifiManager.WIFI_STATE_DISABLING: + removeConnectedWifiEntryPreference(); + removeWifiEntryPreference(); + addMessagePreference(R.string.wifi_stopping); + break; + + case WifiManager.WIFI_STATE_DISABLED: + setOffMessage(); + setAdditionalSettingsSummaries(); + setProgressBarVisible(false); + mClickedConnect = false; + break; + } + } + + @Override + public void onWifiEntriesChanged() { + updateWifiEntryPreferencesDelayed(); + changeNextButtonState(mWifiPickerTracker.getConnectedWifiEntry() != null); + + // Edit the Wi-Fi network of specified SSID. + if (mOpenSsid != null) { + Optional matchedWifiEntry = mWifiPickerTracker.getWifiEntries().stream() + .filter(wifiEntry -> TextUtils.equals(mOpenSsid, wifiEntry.getSsid())) + .filter(wifiEntry -> wifiEntry.getSecurity() != WifiEntry.SECURITY_NONE + && wifiEntry.getSecurity() != WifiEntry.SECURITY_OWE) + .filter(wifiEntry -> !wifiEntry.isSaved() + || isDisabledByWrongPassword(wifiEntry)) + .findFirst(); + if (matchedWifiEntry.isPresent()) { + mOpenSsid = null; + launchConfigNewNetworkFragment(matchedWifiEntry.get()); + } + } + } + + @Override + public void onNumSavedNetworksChanged() { + if (isFinishingOrDestroyed()) { + return; + } + setAdditionalSettingsSummaries(); + } + + @Override + public void onNumSavedSubscriptionsChanged() { + if (isFinishingOrDestroyed()) { + return; + } + setAdditionalSettingsSummaries(); + } + + /** + * Updates WifiEntries from {@link WifiPickerTracker#getWifiEntries()}. Adds a delay to have + * progress bar displayed before starting to modify entries. + */ + private void updateWifiEntryPreferencesDelayed() { + // Safeguard from some delayed event handling + if (getActivity() != null && !mIsRestricted + && mWifiPickerTracker.getWifiState() == WifiManager.WIFI_STATE_ENABLED) { + final View view = getView(); + final Handler handler = view.getHandler(); + if (handler != null && handler.hasCallbacks(mUpdateWifiEntryPreferencesRunnable)) { + return; + } + setProgressBarVisible(true); + view.postDelayed(mUpdateWifiEntryPreferencesRunnable, 300); + } + } + + private void updateWifiEntryPreferences() { + // in case state has changed + if (mWifiPickerTracker.getWifiState() != WifiManager.WIFI_STATE_ENABLED) { + return; + } + + boolean hasAvailableWifiEntries = false; + mStatusMessagePreference.setVisible(false); + mWifiEntryPreferenceCategory.setVisible(true); + + final WifiEntry connectedEntry = mWifiPickerTracker.getConnectedWifiEntry(); + mConnectedWifiEntryPreferenceCategory.setVisible(connectedEntry != null); + if (connectedEntry != null) { + final LongPressWifiEntryPreference connectedPref = + mConnectedWifiEntryPreferenceCategory.findPreference(connectedEntry.getKey()); + if (connectedPref == null || connectedPref.getWifiEntry() != connectedEntry) { + mConnectedWifiEntryPreferenceCategory.removeAll(); + final ConnectedWifiEntryPreference pref = + new ConnectedWifiEntryPreference(getPrefContext(), connectedEntry, this); + pref.setKey(connectedEntry.getKey()); + pref.refresh(); + mConnectedWifiEntryPreferenceCategory.addPreference(pref); + pref.setOnPreferenceClickListener(preference -> { + if (connectedEntry.canSignIn()) { + connectedEntry.signIn(null /* callback */); + } else { + launchNetworkDetailsFragment(pref); + } + return true; + }); + pref.setOnGearClickListener(preference -> { + launchNetworkDetailsFragment(pref); + }); + + if (mClickedConnect) { + mClickedConnect = false; + scrollToPreference(mConnectedWifiEntryPreferenceCategory); + } + } + } else { + mConnectedWifiEntryPreferenceCategory.removeAll(); + } + + int index = 0; + cacheRemoveAllPrefs(mWifiEntryPreferenceCategory); + List wifiEntries = mWifiPickerTracker.getWifiEntries(); + for (WifiEntry wifiEntry : wifiEntries) { + hasAvailableWifiEntries = true; + + String key = wifiEntry.getKey(); + LongPressWifiEntryPreference pref = + (LongPressWifiEntryPreference) getCachedPreference(key); + if (pref != null) { + if (pref.getWifiEntry() == wifiEntry) { + pref.setOrder(index++); + continue; + } else { + // Create a new preference if the underlying WifiEntry object has changed + removePreference(key); + } + } + + pref = createLongPressWifiEntryPreference(wifiEntry); + pref.setKey(wifiEntry.getKey()); + pref.setOrder(index++); + pref.refresh(); + + if (wifiEntry.getHelpUriString() != null) { + pref.setOnButtonClickListener(preference -> { + openSubscriptionHelpPage(wifiEntry); + }); + } + mWifiEntryPreferenceCategory.addPreference(pref); + } + removeCachedPrefs(mWifiEntryPreferenceCategory); + + if (!hasAvailableWifiEntries) { + setProgressBarVisible(true); + Preference pref = new Preference(getPrefContext()); + pref.setSelectable(false); + pref.setSummary(R.string.wifi_empty_list_wifi_on); + pref.setOrder(index++); + pref.setKey(PREF_KEY_EMPTY_WIFI_LIST); + mWifiEntryPreferenceCategory.addPreference(pref); + } else { + // Continuing showing progress bar for an additional delay to overlap with animation + getView().postDelayed(mHideProgressBarRunnable, 1700 /* delay millis */); + } + + mAddWifiNetworkPreference.setOrder(index++); + mWifiEntryPreferenceCategory.addPreference(mAddWifiNetworkPreference); + setAdditionalSettingsSummaries(); + } + + private void launchNetworkDetailsFragment(LongPressWifiEntryPreference pref) { + final WifiEntry wifiEntry = pref.getWifiEntry(); + final Context context = getContext(); + final CharSequence title = + FeatureFlagUtils.isEnabled(context, FeatureFlags.WIFI_DETAILS_DATAUSAGE_HEADER) + ? wifiEntry.getTitle() + : context.getText(R.string.pref_title_network_details); + + final Bundle bundle = new Bundle(); + bundle.putString(WifiNetworkDetailsFragment2.KEY_CHOSEN_WIFIENTRY_KEY, wifiEntry.getKey()); + + new SubSettingLauncher(context) + .setTitleText(title) + .setDestination(WifiNetworkDetailsFragment2.class.getName()) + .setArguments(bundle) + .setSourceMetricsCategory(getMetricsCategory()) + .launch(); + } + + @VisibleForTesting + LongPressWifiEntryPreference createLongPressWifiEntryPreference(WifiEntry wifiEntry) { + return new LongPressWifiEntryPreference(getPrefContext(), wifiEntry, this); + } + + private void launchAddNetworkFragment() { + new SubSettingLauncher(getContext()) + .setTitleRes(R.string.wifi_add_network) + .setDestination(AddNetworkFragment.class.getName()) + .setSourceMetricsCategory(getMetricsCategory()) + .setResultListener(this, ADD_NETWORK_REQUEST) + .launch(); + } + + /** Removes all preferences and hide the {@link #mConnectedWifiEntryPreferenceCategory}. */ + private void removeConnectedWifiEntryPreference() { + mConnectedWifiEntryPreferenceCategory.removeAll(); + mConnectedWifiEntryPreferenceCategory.setVisible(false); + } + + private void removeWifiEntryPreference() { + mWifiEntryPreferenceCategory.removeAll(); + mWifiEntryPreferenceCategory.setVisible(false); + } + + @VisibleForTesting + void setAdditionalSettingsSummaries() { + mConfigureWifiSettingsPreference.setSummary(getString( + isWifiWakeupEnabled() + ? R.string.wifi_configure_settings_preference_summary_wakeup_on + : R.string.wifi_configure_settings_preference_summary_wakeup_off)); + + final int numSavedNetworks = mWifiPickerTracker.getNumSavedNetworks(); + final int numSavedSubscriptions = mWifiPickerTracker.getNumSavedSubscriptions(); + if (numSavedNetworks + numSavedSubscriptions > 0) { + mSavedNetworksPreference.setVisible(true); + mSavedNetworksPreference.setSummary( + getSavedNetworkSettingsSummaryText(numSavedNetworks, numSavedSubscriptions)); + } else { + mSavedNetworksPreference.setVisible(false); + } + } + + private String getSavedNetworkSettingsSummaryText( + int numSavedNetworks, int numSavedSubscriptions) { + if (numSavedSubscriptions == 0) { + return getResources().getQuantityString(R.plurals.wifi_saved_access_points_summary, + numSavedNetworks, numSavedNetworks); + } else if (numSavedNetworks == 0) { + return getResources().getQuantityString( + R.plurals.wifi_saved_passpoint_access_points_summary, + numSavedSubscriptions, numSavedSubscriptions); + } else { + final int numTotalEntries = numSavedNetworks + numSavedSubscriptions; + return getResources().getQuantityString(R.plurals.wifi_saved_all_access_points_summary, + numTotalEntries, numTotalEntries); + } + } + + private boolean isWifiWakeupEnabled() { + final Context context = getContext(); + final PowerManager powerManager = context.getSystemService(PowerManager.class); + final ContentResolver contentResolver = context.getContentResolver(); + return mWifiManager.isAutoWakeupEnabled() + && mWifiManager.isScanAlwaysAvailable() + && Settings.Global.getInt(contentResolver, + Settings.Global.AIRPLANE_MODE_ON, 0) == 0 + && !powerManager.isPowerSaveMode(); + } + + private void setOffMessage() { + final CharSequence title = getText(R.string.wifi_empty_list_wifi_off); + // Don't use WifiManager.isScanAlwaysAvailable() to check the Wi-Fi scanning mode. Instead, + // read the system settings directly. Because when the device is in Airplane mode, even if + // Wi-Fi scanning mode is on, WifiManager.isScanAlwaysAvailable() still returns "off". + // TODO(b/149421497): Fix this? + final boolean wifiScanningMode = mWifiManager.isScanAlwaysAvailable(); + final CharSequence description = wifiScanningMode ? getText(R.string.wifi_scan_notify_text) + : getText(R.string.wifi_scan_notify_text_scanning_off); + final LinkifyUtils.OnClickListener clickListener = + () -> new SubSettingLauncher(getContext()) + .setDestination(ScanningSettings.class.getName()) + .setTitleRes(R.string.location_scanning_screen_title) + .setSourceMetricsCategory(getMetricsCategory()) + .launch(); + mStatusMessagePreference.setText(title, description, clickListener); + removeConnectedWifiEntryPreference(); + removeWifiEntryPreference(); + mStatusMessagePreference.setVisible(true); + } + + private void addMessagePreference(int messageId) { + mStatusMessagePreference.setTitle(messageId); + mStatusMessagePreference.setVisible(true); + + } + + protected void setProgressBarVisible(boolean visible) { + if (mProgressHeader != null) { + mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE); + } + } + + @VisibleForTesting + void handleAddNetworkRequest(int result, Intent data) { + if (result == Activity.RESULT_OK) { + handleAddNetworkSubmitEvent(data); + } + } + + private void handleAddNetworkSubmitEvent(Intent data) { + final WifiConfiguration wifiConfiguration = data.getParcelableExtra( + AddNetworkFragment.WIFI_CONFIG_KEY); + if (wifiConfiguration != null) { + mWifiManager.save(wifiConfiguration, mSaveListener); + } + } + + /** + * Called when "add network" button is pressed. + */ + private void onAddNetworkPressed() { + launchAddNetworkFragment(); + } + + @Override + public int getHelpResource() { + return R.string.help_url_wifi; + } + + /** + * Renames/replaces "Next" button when appropriate. "Next" button usually exists in + * Wi-Fi setup screens, not in usual wifi settings screen. + * + * @param enabled true when the device is connected to a wifi network. + */ + @VisibleForTesting + void changeNextButtonState(boolean enabled) { + if (mEnableNextOnConnection && hasNextButton()) { + getNextButton().setEnabled(enabled); + } + } + + @Override + public void onForget(WifiDialog2 dialog) { + forget(dialog.getWifiEntry()); + } + + @Override + public void onSubmit(WifiDialog2 dialog) { + final int dialogMode = dialog.getMode(); + final WifiConfiguration config = dialog.getController().getConfig(); + final WifiEntry wifiEntry = dialog.getWifiEntry(); + + if (dialogMode == WifiConfigUiBase2.MODE_MODIFY) { + if (config == null) { + Toast.makeText(getContext(), R.string.wifi_failed_save_message, + Toast.LENGTH_SHORT).show(); + } else { + mWifiManager.save(config, mSaveListener); + } + } else if (dialogMode == WifiConfigUiBase2.MODE_CONNECT + || (dialogMode == WifiConfigUiBase2.MODE_VIEW && wifiEntry.canConnect())) { + if (config == null) { + connect(wifiEntry, false /* editIfNoConfig */, + false /* fullScreenEdit*/); + } else { + mWifiManager.connect(config, new WifiConnectActionListener()); + } + } + } + + @Override + public void onScan(WifiDialog2 dialog, String ssid) { + // Launch QR code scanner to join a network. + startActivityForResult(WifiDppUtils.getEnrolleeQrCodeScannerIntent(ssid), + REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER); + } + + private void forget(WifiEntry wifiEntry) { + mMetricsFeatureProvider.action(getActivity(), SettingsEnums.ACTION_WIFI_FORGET); + wifiEntry.forget(null /* callback */); + } + + @VisibleForTesting + void connect(WifiEntry wifiEntry, boolean editIfNoConfig, boolean fullScreenEdit) { + mMetricsFeatureProvider.action(getActivity(), SettingsEnums.ACTION_WIFI_CONNECT, + wifiEntry.isSaved()); + + // If it's an unsaved secure WifiEntry, it will callback + // ConnectCallback#onConnectResult with ConnectCallback#CONNECT_STATUS_FAILURE_NO_CONFIG + wifiEntry.connect(new WifiEntryConnectCallback(wifiEntry, editIfNoConfig, + fullScreenEdit)); + } + + private class WifiConnectActionListener implements WifiManager.ActionListener { + @Override + public void onSuccess() { + mClickedConnect = true; + } + + @Override + public void onFailure(int reason) { + if (isFinishingOrDestroyed()) { + return; + } + Toast.makeText(getContext(), R.string.wifi_failed_connect_message, Toast.LENGTH_SHORT) + .show(); + } + }; + + public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = + new BaseSearchIndexProvider(R.xml.network_provider_settings) { + @Override + public List getNonIndexableKeys(Context context) { + final List keys = super.getNonIndexableKeys(context); + + final WifiManager wifiManager = context.getSystemService(WifiManager.class); + if (WifiSavedConfigUtils.getAllConfigsCount(context, wifiManager) == 0) { + keys.add(PREF_KEY_SAVED_NETWORKS); + } + + if (!DataUsageUtils.hasWifiRadio(context)) { + keys.add(PREF_KEY_DATA_USAGE); + } + return keys; + } + }; + + private class WifiEntryConnectCallback implements ConnectCallback { + final WifiEntry mConnectWifiEntry; + final boolean mEditIfNoConfig; + final boolean mFullScreenEdit; + + WifiEntryConnectCallback(WifiEntry connectWifiEntry, boolean editIfNoConfig, + boolean fullScreenEdit) { + mConnectWifiEntry = connectWifiEntry; + mEditIfNoConfig = editIfNoConfig; + mFullScreenEdit = fullScreenEdit; + } + + @Override + public void onConnectResult(@ConnectStatus int status) { + if (isFinishingOrDestroyed()) { + return; + } + + if (status == ConnectCallback.CONNECT_STATUS_SUCCESS) { + mClickedConnect = true; + } else if (status == ConnectCallback.CONNECT_STATUS_FAILURE_NO_CONFIG) { + if (mEditIfNoConfig) { + // Edit an unsaved secure Wi-Fi network. + if (mFullScreenEdit) { + launchConfigNewNetworkFragment(mConnectWifiEntry); + } else { + showDialog(mConnectWifiEntry, WifiConfigUiBase2.MODE_CONNECT); + } + } + } else if (status == CONNECT_STATUS_FAILURE_UNKNOWN) { + Toast.makeText(getContext(), R.string.wifi_failed_connect_message, + Toast.LENGTH_SHORT).show(); + } + } + } + + private void launchConfigNewNetworkFragment(WifiEntry wifiEntry) { + final Bundle bundle = new Bundle(); + bundle.putString(WifiNetworkDetailsFragment2.KEY_CHOSEN_WIFIENTRY_KEY, + wifiEntry.getKey()); + new SubSettingLauncher(getContext()) + .setTitleText(wifiEntry.getTitle()) + .setDestination(ConfigureWifiEntryFragment.class.getName()) + .setArguments(bundle) + .setSourceMetricsCategory(getMetricsCategory()) + .setResultListener(NetworkProviderSettings.this, CONFIG_NETWORK_REQUEST) + .launch(); + } + + /** Helper method to return whether a WifiEntry is disabled due to a wrong password */ + private static boolean isDisabledByWrongPassword(WifiEntry wifiEntry) { + WifiConfiguration config = wifiEntry.getWifiConfiguration(); + if (config == null) { + return false; + } + WifiConfiguration.NetworkSelectionStatus networkStatus = + config.getNetworkSelectionStatus(); + if (networkStatus == null + || networkStatus.getNetworkSelectionStatus() == NETWORK_SELECTION_ENABLED) { + return false; + } + int reason = networkStatus.getNetworkSelectionDisableReason(); + return WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD == reason; + } + + @VisibleForTesting + void openSubscriptionHelpPage(WifiEntry wifiEntry) { + final Intent intent = getHelpIntent(getContext(), wifiEntry.getHelpUriString()); + if (intent != null) { + try { + startActivityForResult(intent, MANAGE_SUBSCRIPTION); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "Activity was not found for intent, " + intent.toString()); + } + } + } + + @VisibleForTesting + Intent getHelpIntent(Context context, String helpUrlString) { + return HelpUtils.getHelpIntent(context, helpUrlString, context.getClass().getName()); + } +} diff --git a/src/com/android/settings/wifi/AddNetworkFragment.java b/src/com/android/settings/wifi/AddNetworkFragment.java index 7b869361692..2ed213b8d55 100644 --- a/src/com/android/settings/wifi/AddNetworkFragment.java +++ b/src/com/android/settings/wifi/AddNetworkFragment.java @@ -41,7 +41,7 @@ import com.android.settings.wifi.dpp.WifiDppUtils; public class AddNetworkFragment extends InstrumentedFragment implements WifiConfigUiBase2, View.OnClickListener { - final static String WIFI_CONFIG_KEY = "wifi_config_key"; + public static final String WIFI_CONFIG_KEY = "wifi_config_key"; @VisibleForTesting final static int SUBMIT_BUTTON_ID = android.R.id.button1; @VisibleForTesting diff --git a/src/com/android/settings/wifi/WifiSettings.java b/src/com/android/settings/wifi/WifiSettings.java index 7bc40638312..5c7820c6abb 100644 --- a/src/com/android/settings/wifi/WifiSettings.java +++ b/src/com/android/settings/wifi/WifiSettings.java @@ -88,6 +88,9 @@ import java.util.Optional; /** * UI for Wi-Fi settings screen + * + * TODO(b/167474581): This file will be deprecated at Android S, please merge your WifiSettings + * in change in {@link NetworkProviderSettings}. */ @SearchIndexable public class WifiSettings extends RestrictedSettingsFragment @@ -228,6 +231,17 @@ public class WifiSettings extends RestrictedSettingsFragment public void onCreate(Bundle icicle) { super.onCreate(icicle); + if (FeatureFlagUtils.isEnabled(getContext(), FeatureFlagUtils.SETTINGS_PROVIDER_MODEL)) { + final Intent intent = new Intent("android.settings.NETWORK_PROVIDER_SETTINGS"); + final Bundle extras = getActivity().getIntent().getExtras(); + if (extras != null) { + intent.putExtras(extras); + } + getContext().startActivity(intent); + finish(); + return; + } + // TODO(b/37429702): Add animations and preference comparator back after initial screen is // loaded (ODR). setAnimationAllowed(false); diff --git a/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java b/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java new file mode 100644 index 00000000000..90018b5a232 --- /dev/null +++ b/tests/robotests/src/com/android/settings/network/NetworkProviderSettingsTest.java @@ -0,0 +1,377 @@ +/* + * Copyright (C) 2020 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.network; + +import static com.android.settings.wifi.WifiConfigUiBase2.MODE_CONNECT; +import static com.android.settings.wifi.WifiConfigUiBase2.MODE_MODIFY; + +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.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +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.app.Activity; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.net.wifi.WifiConfiguration; +import android.net.wifi.WifiManager; +import android.os.Bundle; +import android.os.PowerManager; +import android.os.UserManager; +import android.provider.Settings; +import android.view.ContextMenu; +import android.view.View; + +import androidx.fragment.app.FragmentActivity; +import androidx.preference.Preference; +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.settings.R; +import com.android.settings.datausage.DataUsagePreference; +import com.android.settings.testutils.shadow.ShadowDataUsageUtils; +import com.android.settings.testutils.shadow.ShadowFragment; +import com.android.settings.wifi.AddWifiNetworkPreference; +import com.android.settings.wifi.WifiConfigController2; +import com.android.settings.wifi.WifiDialog2; +import com.android.settingslib.wifi.LongPressWifiEntryPreference; +import com.android.wifitrackerlib.WifiEntry; +import com.android.wifitrackerlib.WifiPickerTracker; + +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; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.shadows.ShadowToast; + +@RunWith(RobolectricTestRunner.class) +public class NetworkProviderSettingsTest { + + private static final int NUM_NETWORKS = 4; + private static final String FAKE_URI_STRING = "fakeuri"; + + @Mock + private PowerManager mPowerManager; + @Mock + private WifiManager mWifiManager; + @Mock + private DataUsagePreference mDataUsagePreference; + private Context mContext; + private NetworkProviderSettings mNetworkProviderSettings; + @Mock + private WifiPickerTracker mMockWifiPickerTracker; + @Mock + private PreferenceManager mPreferenceManager; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mContext = spy(RuntimeEnvironment.application); + + mNetworkProviderSettings = spy(new NetworkProviderSettings()); + doReturn(mContext).when(mNetworkProviderSettings).getContext(); + doReturn(mPreferenceManager).when(mNetworkProviderSettings).getPreferenceManager(); + doReturn(mPowerManager).when(mContext).getSystemService(PowerManager.class); + doReturn(mWifiManager).when(mContext).getSystemService(WifiManager.class); + doReturn(mContext).when(mPreferenceManager).getContext(); + mNetworkProviderSettings.mAddWifiNetworkPreference = new AddWifiNetworkPreference(mContext); + mNetworkProviderSettings.mSavedNetworksPreference = new Preference(mContext); + mNetworkProviderSettings.mConfigureWifiSettingsPreference = + new Preference(mContext); + mNetworkProviderSettings.mWifiPickerTracker = mMockWifiPickerTracker; + mNetworkProviderSettings.mWifiManager = mWifiManager; + } + + @Test + public void addNetworkFragmentSendResult_onActivityResult_shouldHandleEvent() { + final NetworkProviderSettings NetworkProviderSettings = spy(new NetworkProviderSettings()); + final Intent intent = new Intent(); + doNothing().when(NetworkProviderSettings).handleAddNetworkRequest(anyInt(), + any(Intent.class)); + + NetworkProviderSettings.onActivityResult(NetworkProviderSettings.ADD_NETWORK_REQUEST, + Activity.RESULT_OK, intent); + + verify(NetworkProviderSettings).handleAddNetworkRequest(anyInt(), any(Intent.class)); + } + + @Test + public void setAdditionalSettingsSummaries_hasSavedNetwork_preferenceVisible() { + when(mMockWifiPickerTracker.getNumSavedNetworks()).thenReturn(NUM_NETWORKS); + when(mMockWifiPickerTracker.getNumSavedSubscriptions()).thenReturn(0 /* count */); + + mNetworkProviderSettings.setAdditionalSettingsSummaries(); + + assertThat(mNetworkProviderSettings.mSavedNetworksPreference.isVisible()).isTrue(); + assertThat(mNetworkProviderSettings.mSavedNetworksPreference.getSummary()).isEqualTo( + mContext.getResources().getQuantityString( + R.plurals.wifi_saved_access_points_summary, + NUM_NETWORKS, NUM_NETWORKS)); + } + + @Test + public void setAdditionalSettingsSummaries_hasSavedPasspointNetwork_preferenceVisible() { + when(mMockWifiPickerTracker.getNumSavedNetworks()).thenReturn(0 /* count */); + when(mMockWifiPickerTracker.getNumSavedSubscriptions()).thenReturn(NUM_NETWORKS); + + mNetworkProviderSettings.setAdditionalSettingsSummaries(); + + assertThat(mNetworkProviderSettings.mSavedNetworksPreference.isVisible()).isTrue(); + assertThat(mNetworkProviderSettings.mSavedNetworksPreference.getSummary()).isEqualTo( + mContext.getResources().getQuantityString( + R.plurals.wifi_saved_passpoint_access_points_summary, + NUM_NETWORKS, NUM_NETWORKS)); + } + + @Test + public void setAdditionalSettingsSummaries_hasTwoKindsSavedNetwork_preferenceVisible() { + when(mMockWifiPickerTracker.getNumSavedNetworks()).thenReturn(NUM_NETWORKS); + when(mMockWifiPickerTracker.getNumSavedSubscriptions()).thenReturn(NUM_NETWORKS); + + mNetworkProviderSettings.setAdditionalSettingsSummaries(); + + assertThat(mNetworkProviderSettings.mSavedNetworksPreference.isVisible()).isTrue(); + assertThat(mNetworkProviderSettings.mSavedNetworksPreference.getSummary()).isEqualTo( + mContext.getResources().getQuantityString( + R.plurals.wifi_saved_all_access_points_summary, + NUM_NETWORKS * 2, NUM_NETWORKS * 2)); + } + + @Test + public void setAdditionalSettingsSummaries_noSavedNetwork_preferenceInvisible() { + when(mMockWifiPickerTracker.getNumSavedNetworks()).thenReturn(0 /* count */); + when(mMockWifiPickerTracker.getNumSavedSubscriptions()).thenReturn(0 /* count */); + + mNetworkProviderSettings.setAdditionalSettingsSummaries(); + + assertThat(mNetworkProviderSettings.mSavedNetworksPreference.isVisible()).isFalse(); + } + + @Test + public void setAdditionalSettingsSummaries_wifiWakeupEnabled_displayOn() { + final ContentResolver contentResolver = mContext.getContentResolver(); + when(mWifiManager.isAutoWakeupEnabled()).thenReturn(true); + when(mWifiManager.isScanAlwaysAvailable()).thenReturn(true); + Settings.Global.putInt(contentResolver, Settings.Global.AIRPLANE_MODE_ON, 0); + when(mPowerManager.isPowerSaveMode()).thenReturn(false); + + mNetworkProviderSettings.setAdditionalSettingsSummaries(); + + assertThat(mNetworkProviderSettings + .mConfigureWifiSettingsPreference.getSummary()).isEqualTo( + mContext.getString(R.string.wifi_configure_settings_preference_summary_wakeup_on)); + } + + @Test + public void setAdditionalSettingsSummaries_wifiWakeupDisabled_displayOff() { + final ContentResolver contentResolver = mContext.getContentResolver(); + when(mWifiManager.isAutoWakeupEnabled()).thenReturn(false); + + mNetworkProviderSettings.setAdditionalSettingsSummaries(); + + assertThat(mNetworkProviderSettings + .mConfigureWifiSettingsPreference.getSummary()).isEqualTo( + mContext.getString(R.string.wifi_configure_settings_preference_summary_wakeup_off)); + } + + @Test + public void checkAddWifiNetworkPreference_preferenceVisible() { + assertThat(mNetworkProviderSettings.mAddWifiNetworkPreference.isVisible()).isTrue(); + assertThat(mNetworkProviderSettings.mAddWifiNetworkPreference.getTitle()).isEqualTo( + mContext.getString(R.string.wifi_add_network)); + } + + private void setUpForOnCreate() { + final FragmentActivity activity = mock(FragmentActivity.class); + when(mNetworkProviderSettings.getActivity()).thenReturn(activity); + final Resources.Theme theme = mContext.getTheme(); + when(activity.getTheme()).thenReturn(theme); + UserManager userManager = mock(UserManager.class); + when(activity.getSystemService(Context.USER_SERVICE)) + .thenReturn(userManager); + + when(mNetworkProviderSettings.findPreference(NetworkProviderSettings.PREF_KEY_DATA_USAGE)) + .thenReturn(mDataUsagePreference); + } + + @Test + @Config(shadows = {ShadowDataUsageUtils.class, ShadowFragment.class}) + public void checkDataUsagePreference_perferenceInvisibleIfWifiNotSupported() { + setUpForOnCreate(); + ShadowDataUsageUtils.IS_WIFI_SUPPORTED = false; + + mNetworkProviderSettings.onCreate(Bundle.EMPTY); + + verify(mDataUsagePreference).setVisible(false); + } + + @Test + @Config(shadows = {ShadowDataUsageUtils.class, ShadowFragment.class}) + public void checkDataUsagePreference_perferenceVisibleIfWifiSupported() { + setUpForOnCreate(); + ShadowDataUsageUtils.IS_WIFI_SUPPORTED = true; + + mNetworkProviderSettings.onCreate(Bundle.EMPTY); + + verify(mDataUsagePreference).setVisible(true); + verify(mDataUsagePreference).setTemplate(any(), eq(0) /*subId*/, eq(null) /*service*/); + } + + @Test + public void onCreateAdapter_hasStableIdsTrue() { + final PreferenceScreen preferenceScreen = mock(PreferenceScreen.class); + when(preferenceScreen.getContext()).thenReturn(mContext); + + RecyclerView.Adapter adapter = mNetworkProviderSettings.onCreateAdapter(preferenceScreen); + + assertThat(adapter.hasStableIds()).isTrue(); + } + + @Test + public void onCreateContextMenu_shouldHaveForgetAndDisconnectMenuForConnectedWifiEntry() { + final FragmentActivity activity = mock(FragmentActivity.class); + when(activity.getApplicationContext()).thenReturn(mContext); + when(mNetworkProviderSettings.getActivity()).thenReturn(activity); + + final WifiEntry wifiEntry = mock(WifiEntry.class); + when(wifiEntry.canDisconnect()).thenReturn(true); + when(wifiEntry.canForget()).thenReturn(true); + when(wifiEntry.isSaved()).thenReturn(true); + when(wifiEntry.getConnectedState()).thenReturn(WifiEntry.CONNECTED_STATE_CONNECTED); + + final LongPressWifiEntryPreference connectedWifiEntryPreference = + mNetworkProviderSettings.createLongPressWifiEntryPreference(wifiEntry); + final View view = mock(View.class); + when(view.getTag()).thenReturn(connectedWifiEntryPreference); + + final ContextMenu menu = mock(ContextMenu.class); + mNetworkProviderSettings.onCreateContextMenu(menu, view, null /* info */); + + verify(menu).add(anyInt(), eq(NetworkProviderSettings.MENU_ID_FORGET), anyInt(), anyInt()); + verify(menu).add(anyInt(), eq(NetworkProviderSettings.MENU_ID_DISCONNECT), anyInt(), + anyInt()); + } + + @Test + public void onWifiEntriesChanged_shouldChangeNextButtonState() { + mNetworkProviderSettings.onWifiEntriesChanged(); + + verify(mNetworkProviderSettings).changeNextButtonState(anyBoolean()); + } + + @Test + public void openSubscriptionHelpPage_shouldCallStartActivityForResult() { + doReturn(new Intent()).when(mNetworkProviderSettings).getHelpIntent(mContext, + FAKE_URI_STRING); + doNothing().when(mNetworkProviderSettings).startActivityForResult(any(Intent.class), + anyInt()); + final WifiEntry mockWifiEntry = mock(WifiEntry.class); + when(mockWifiEntry.getHelpUriString()).thenReturn(FAKE_URI_STRING); + + mNetworkProviderSettings.openSubscriptionHelpPage(mockWifiEntry); + + verify(mNetworkProviderSettings, times(1)).startActivityForResult(any(), anyInt()); + } + + @Test + public void onNumSavedNetworksChanged_isFinishing_ShouldNotCrash() { + final FragmentActivity activity = mock(FragmentActivity.class); + when(activity.isFinishing()).thenReturn(true); + when(mNetworkProviderSettings.getActivity()).thenReturn(activity); + when(mNetworkProviderSettings.getContext()).thenReturn(null); + + mNetworkProviderSettings.onNumSavedNetworksChanged(); + } + + @Test + public void onNumSavedSubscriptionsChanged_isFinishing_ShouldNotCrash() { + final FragmentActivity activity = mock(FragmentActivity.class); + when(activity.isFinishing()).thenReturn(true); + when(mNetworkProviderSettings.getActivity()).thenReturn(activity); + when(mNetworkProviderSettings.getContext()).thenReturn(null); + + mNetworkProviderSettings.onNumSavedSubscriptionsChanged(); + } + + @Test + public void onSubmit_modeModifyNoConfig_toastErrorMessage() { + WifiDialog2 dialog = createWifiDialog2(MODE_MODIFY, null /* config */); + + mNetworkProviderSettings.onSubmit(dialog); + + assertThat(ShadowToast.getTextOfLatestToast()).isEqualTo( + mContext.getString(R.string.wifi_failed_save_message)); + } + + @Test + public void onSubmit_modeModifyHasConfig_saveWifiManager() { + final WifiConfiguration config = mock(WifiConfiguration.class); + WifiDialog2 dialog = createWifiDialog2(MODE_MODIFY, config); + + mNetworkProviderSettings.onSubmit(dialog); + + verify(mWifiManager).save(eq(config), any()); + } + + @Test + public void onSubmit_modeConnectNoConfig_connectWifiEntry() { + WifiDialog2 dialog = createWifiDialog2(MODE_CONNECT, null /* config */); + final WifiEntry wifiEntry = dialog.getWifiEntry(); + + mNetworkProviderSettings.onAttach(mContext); + mNetworkProviderSettings.onSubmit(dialog); + + verify(mNetworkProviderSettings).connect(wifiEntry, false /* editIfNoConfig */, + false /* fullScreenEdit*/); + } + + @Test + public void onSubmit_modeConnectHasConfig_connectWifiManager() { + final WifiConfiguration config = mock(WifiConfiguration.class); + WifiDialog2 dialog = createWifiDialog2(MODE_CONNECT, config); + + mNetworkProviderSettings.onSubmit(dialog); + + verify(mWifiManager).connect(eq(config), any(WifiManager.ActionListener.class)); + } + + private WifiDialog2 createWifiDialog2(int mode, WifiConfiguration config) { + final WifiEntry wifiEntry = mock(WifiEntry.class); + when(wifiEntry.canConnect()).thenReturn(true); + final WifiConfigController2 controller = mock(WifiConfigController2.class); + when(controller.getConfig()).thenReturn(config); + final WifiDialog2 wifiDialog2 = spy(WifiDialog2.createModal(mContext, null /* listener */, + wifiEntry, mode)); + when(wifiDialog2.getController()).thenReturn(controller); + return wifiDialog2; + } +}