blob: 280439326d32ee8ef59b858b08f394895e629db0 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings.wifi;
import 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.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 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
implements Indexable, WifiPickerTracker.WifiPickerTrackerCallback,
WifiDialog2.WifiDialog2Listener, DialogInterface.OnDismissListener {
private static final String TAG = "WifiSettings";
// 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;
static final int MENU_ID_SHARE = Menu.FIRST + 5;
// 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 neccesary 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 WifiSettings() {
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);
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);
addPreferences();
mIsRestricted = isUiRestricted();
}
private void addPreferences() {
addPreferencesFromResource(R.xml.wifi_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_SHARE, 0 /* order */, R.string.share);
menu.add(Menu.NONE, MENU_ID_DISCONNECT, 1 /* 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_SHARE:
WifiDppUtils.showLockScreen(getContext(),
() -> launchWifiDppConfiguratorActivity(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<WifiEntry> 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<WifiEntry> 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.wifi_settings) {
@Override
public List<String> getNonIndexableKeys(Context context) {
final List<String> 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(WifiSettings.this, CONFIG_NETWORK_REQUEST)
.launch();
}
private void launchWifiDppConfiguratorActivity(WifiEntry wifiEntry) {
final Intent intent = WifiDppUtils.getConfiguratorQrCodeGeneratorIntentOrNull(getContext(),
mWifiManager, wifiEntry);
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);
startActivity(intent);
}
}
/** 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());
}
}