2 * Copyright (C) 2010 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.settings.wifi;
19 import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
21 import android.annotation.NonNull;
22 import android.app.Activity;
23 import android.app.Dialog;
24 import android.app.admin.DevicePolicyManager;
25 import android.content.ComponentName;
26 import android.content.ContentResolver;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.pm.PackageManager;
30 import android.content.pm.PackageManager.NameNotFoundException;
31 import android.content.res.Resources;
32 import android.net.ConnectivityManager;
33 import android.net.NetworkInfo;
34 import android.net.NetworkInfo.State;
35 import android.net.wifi.WifiConfiguration;
36 import android.net.wifi.WifiManager;
37 import android.net.wifi.WpsInfo;
38 import android.nfc.NfcAdapter;
39 import android.os.Bundle;
40 import android.os.HandlerThread;
41 import android.os.Process;
42 import android.provider.Settings;
43 import android.support.annotation.VisibleForTesting;
44 import android.support.v7.preference.Preference;
45 import android.support.v7.preference.PreferenceCategory;
46 import android.support.v7.preference.PreferenceManager;
47 import android.text.TextUtils;
48 import android.util.Log;
49 import android.view.ContextMenu;
50 import android.view.ContextMenu.ContextMenuInfo;
51 import android.view.Menu;
52 import android.view.MenuItem;
53 import android.view.View;
54 import android.widget.ProgressBar;
55 import android.widget.Toast;
57 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
58 import com.android.settings.LinkifyUtils;
59 import com.android.settings.R;
60 import com.android.settings.RestrictedSettingsFragment;
61 import com.android.settings.SettingsActivity;
62 import com.android.settings.dashboard.SummaryLoader;
63 import com.android.settings.location.ScanningSettings;
64 import com.android.settings.search.BaseSearchIndexProvider;
65 import com.android.settings.search.Indexable;
66 import com.android.settings.search.SearchIndexableRaw;
67 import com.android.settings.widget.SummaryUpdater.OnSummaryChangeListener;
68 import com.android.settings.widget.SwitchBarController;
69 import com.android.settings.wifi.details.WifiNetworkDetailsFragment;
70 import com.android.settingslib.RestrictedLockUtils;
71 import com.android.settingslib.wifi.AccessPoint;
72 import com.android.settingslib.wifi.AccessPoint.AccessPointListener;
73 import com.android.settingslib.wifi.AccessPointPreference;
74 import com.android.settingslib.wifi.WifiTracker;
75 import com.android.settingslib.wifi.WifiTrackerFactory;
77 import java.util.ArrayList;
78 import java.util.List;
81 * Two types of UI are provided here.
83 * The first is for "usual Settings", appearing as any other Setup fragment.
85 * The second is for Setup Wizard, with a simplified interface that hides the action bar
88 public class WifiSettings extends RestrictedSettingsFragment
89 implements Indexable, WifiTracker.WifiListener, AccessPointListener,
90 WifiDialog.WifiDialogListener {
92 private static final String TAG = "WifiSettings";
93 private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
95 /* package */ static final int MENU_ID_WPS_PBC = Menu.FIRST;
96 private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1;
97 private static final int MENU_ID_CONNECT = Menu.FIRST + 6;
98 private static final int MENU_ID_FORGET = Menu.FIRST + 7;
99 private static final int MENU_ID_MODIFY = Menu.FIRST + 8;
100 private static final int MENU_ID_WRITE_NFC = Menu.FIRST + 9;
102 public static final int WIFI_DIALOG_ID = 1;
103 /* package */ static final int WPS_PBC_DIALOG_ID = 2;
104 private static final int WPS_PIN_DIALOG_ID = 3;
105 private static final int WRITE_NFC_DIALOG_ID = 6;
107 // Instance state keys
108 private static final String SAVE_DIALOG_MODE = "dialog_mode";
109 private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
110 private static final String SAVED_WIFI_NFC_DIALOG_STATE = "wifi_nfc_dlg_state";
112 private static final String PREF_KEY_EMPTY_WIFI_LIST = "wifi_empty_list";
113 private static final String PREF_KEY_CONNECTED_ACCESS_POINTS = "connected_access_point";
114 private static final String PREF_KEY_ACCESS_POINTS = "access_points";
115 private static final String PREF_KEY_ADDITIONAL_SETTINGS = "additional_settings";
116 private static final String PREF_KEY_CONFIGURE_WIFI_SETTINGS = "configure_settings";
117 private static final String PREF_KEY_SAVED_NETWORKS = "saved_networks";
119 private final Runnable mUpdateAccessPointsRunnable = () -> {
120 updateAccessPointPreferences();
122 private final Runnable mHideProgressBarRunnable = () -> {
123 setProgressBarVisible(false);
126 protected WifiManager mWifiManager;
127 private WifiManager.ActionListener mConnectListener;
128 private WifiManager.ActionListener mSaveListener;
129 private WifiManager.ActionListener mForgetListener;
131 private WifiEnabler mWifiEnabler;
132 // An access point being editted is stored here.
133 private AccessPoint mSelectedAccessPoint;
135 private WifiDialog mDialog;
136 private WriteWifiConfigToNfcDialog mWifiToNfcDialog;
138 private ProgressBar mProgressHeader;
140 // this boolean extra specifies whether to disable the Next button when not connected. Used by
141 // account creation outside of setup wizard.
142 private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
143 // This string extra specifies a network to open the connect dialog on, so the user can enter
144 // network credentials. This is used by quick settings for secured networks.
145 private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
147 // should Next button only be enabled when we have a connection?
148 private boolean mEnableNextOnConnection;
150 // Save the dialog details
151 private int mDialogMode;
152 private AccessPoint mDlgAccessPoint;
153 private Bundle mAccessPointSavedState;
154 private Bundle mWifiNfcDialogSavedState;
156 private WifiTracker mWifiTracker;
157 private String mOpenSsid;
159 private HandlerThread mBgThread;
161 private AccessPointPreference.UserBadgeCache mUserBadgeCache;
163 private PreferenceCategory mConnectedAccessPointPreferenceCategory;
164 private PreferenceCategory mAccessPointsPreferenceCategory;
165 private PreferenceCategory mAdditionalSettingsPreferenceCategory;
166 private Preference mAddPreference;
167 private Preference mConfigureWifiSettingsPreference;
168 private Preference mSavedNetworksPreference;
169 private LinkablePreference mStatusMessagePreference;
172 private static final String DATA_KEY_REFERENCE = "main_toggle_wifi";
174 /* End of "used in Wifi Setup context" */
176 public WifiSettings() {
177 super(DISALLOW_CONFIG_WIFI);
181 public void onViewCreated(View view, Bundle savedInstanceState) {
182 super.onViewCreated(view, savedInstanceState);
183 final Activity activity = getActivity();
184 if (activity != null) {
185 mProgressHeader = (ProgressBar) setPinnedHeaderView(R.layout.wifi_progress_header);
186 setProgressBarVisible(false);
191 public void onCreate(Bundle icicle) {
192 super.onCreate(icicle);
194 // TODO(b/37429702): Add animations and preference comparator back after initial screen is
196 setAnimationAllowed(false);
198 addPreferencesFromResource(R.xml.wifi_settings);
200 mConnectedAccessPointPreferenceCategory =
201 (PreferenceCategory) findPreference(PREF_KEY_CONNECTED_ACCESS_POINTS);
202 mAccessPointsPreferenceCategory =
203 (PreferenceCategory) findPreference(PREF_KEY_ACCESS_POINTS);
204 mAdditionalSettingsPreferenceCategory =
205 (PreferenceCategory) findPreference(PREF_KEY_ADDITIONAL_SETTINGS);
206 mConfigureWifiSettingsPreference = findPreference(PREF_KEY_CONFIGURE_WIFI_SETTINGS);
207 mSavedNetworksPreference = findPreference(PREF_KEY_SAVED_NETWORKS);
209 Context prefContext = getPrefContext();
210 mAddPreference = new Preference(prefContext);
211 mAddPreference.setIcon(R.drawable.ic_menu_add_inset);
212 mAddPreference.setTitle(R.string.wifi_add_network);
213 mStatusMessagePreference = new LinkablePreference(prefContext);
215 mUserBadgeCache = new AccessPointPreference.UserBadgeCache(getPackageManager());
217 if (isUiRestricted()) {
218 getPreferenceScreen().removePreference(mAdditionalSettingsPreferenceCategory);
219 addMessagePreference(R.string.wifi_empty_list_user_restricted);
222 mBgThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
227 public void onDestroy() {
233 public void onActivityCreated(Bundle savedInstanceState) {
234 super.onActivityCreated(savedInstanceState);
236 mWifiTracker = WifiTrackerFactory.create(
237 getActivity(), this, mBgThread.getLooper(), true, true, false);
238 mWifiManager = mWifiTracker.getManager();
240 mConnectListener = new WifiManager.ActionListener() {
242 public void onSuccess() {
245 public void onFailure(int reason) {
246 Activity activity = getActivity();
247 if (activity != null) {
248 Toast.makeText(activity,
249 R.string.wifi_failed_connect_message,
250 Toast.LENGTH_SHORT).show();
255 mSaveListener = new WifiManager.ActionListener() {
257 public void onSuccess() {
260 public void onFailure(int reason) {
261 Activity activity = getActivity();
262 if (activity != null) {
263 Toast.makeText(activity,
264 R.string.wifi_failed_save_message,
265 Toast.LENGTH_SHORT).show();
270 mForgetListener = new WifiManager.ActionListener() {
272 public void onSuccess() {
275 public void onFailure(int reason) {
276 Activity activity = getActivity();
277 if (activity != null) {
278 Toast.makeText(activity,
279 R.string.wifi_failed_forget_message,
280 Toast.LENGTH_SHORT).show();
285 if (savedInstanceState != null) {
286 mDialogMode = savedInstanceState.getInt(SAVE_DIALOG_MODE);
287 if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
288 mAccessPointSavedState =
289 savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
292 if (savedInstanceState.containsKey(SAVED_WIFI_NFC_DIALOG_STATE)) {
293 mWifiNfcDialogSavedState =
294 savedInstanceState.getBundle(SAVED_WIFI_NFC_DIALOG_STATE);
298 // if we're supposed to enable/disable the Next button based on our current connection
299 // state, start it off in the right state
300 Intent intent = getActivity().getIntent();
301 mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
303 if (mEnableNextOnConnection) {
304 if (hasNextButton()) {
305 final ConnectivityManager connectivity = (ConnectivityManager)
306 getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
307 if (connectivity != null) {
308 NetworkInfo info = connectivity.getNetworkInfo(
309 ConnectivityManager.TYPE_WIFI);
310 changeNextButtonState(info.isConnected());
315 registerForContextMenu(getListView());
316 setHasOptionsMenu(true);
318 if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) {
319 mOpenSsid = intent.getStringExtra(EXTRA_START_CONNECT_SSID);
320 updateAccessPointsDelayed();
325 public void onDestroyView() {
326 super.onDestroyView();
328 if (mWifiEnabler != null) {
329 mWifiEnabler.teardownSwitchController();
334 public void onStart() {
337 // On/off switch is hidden for Setup Wizard (returns null)
338 mWifiEnabler = createWifiEnabler();
340 mWifiTracker.startTracking();
342 if (!isUiRestricted() && mWifiManager.isWifiEnabled()) {
343 setProgressBarVisible(true);
344 mWifiTracker.forceUpdate();
345 if (DEBUG) Log.d(TAG, "WifiSettings onStart APs: " + mWifiTracker.getAccessPoints());
347 getView().removeCallbacks(mUpdateAccessPointsRunnable);
348 updateAccessPointPreferences();
353 * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard)
355 private WifiEnabler createWifiEnabler() {
356 final SettingsActivity activity = (SettingsActivity) getActivity();
357 return new WifiEnabler(activity, new SwitchBarController(activity.getSwitchBar()),
358 mMetricsFeatureProvider);
362 public void onResume() {
363 final Activity activity = getActivity();
365 if (mWifiEnabler != null) {
366 mWifiEnabler.resume(activity);
371 public void onPause() {
373 if (mWifiEnabler != null) {
374 mWifiEnabler.pause();
379 public void onStop() {
380 mWifiTracker.stopTracking();
381 getView().removeCallbacks(mUpdateAccessPointsRunnable);
382 getView().removeCallbacks(mHideProgressBarRunnable);
387 public int getMetricsCategory() {
388 return MetricsEvent.WIFI;
392 public void onSaveInstanceState(Bundle outState) {
393 super.onSaveInstanceState(outState);
395 // If the dialog is showing, save its state.
396 if (mDialog != null && mDialog.isShowing()) {
397 outState.putInt(SAVE_DIALOG_MODE, mDialogMode);
398 if (mDlgAccessPoint != null) {
399 mAccessPointSavedState = new Bundle();
400 mDlgAccessPoint.saveWifiState(mAccessPointSavedState);
401 outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
405 if (mWifiToNfcDialog != null && mWifiToNfcDialog.isShowing()) {
406 Bundle savedState = new Bundle();
407 mWifiToNfcDialog.saveState(savedState);
408 outState.putBundle(SAVED_WIFI_NFC_DIALOG_STATE, savedState);
413 public boolean onOptionsItemSelected(MenuItem item) {
414 // If the user is not allowed to configure wifi, do not handle menu selections.
415 if (isUiRestricted()) return false;
417 switch (item.getItemId()) {
418 case MENU_ID_WPS_PBC:
419 showDialog(WPS_PBC_DIALOG_ID);
423 if (getActivity() instanceof SettingsActivity) {
424 ((SettingsActivity) getActivity()).startPreferencePanel(
425 WifiP2pSettings.class.getCanonicalName(),
427 R.string.wifi_p2p_settings_title, null,
430 startFragment(this, WifiP2pSettings.class.getCanonicalName(),
431 R.string.wifi_p2p_settings_title, -1, null);
435 case MENU_ID_WPS_PIN:
436 showDialog(WPS_PIN_DIALOG_ID);
439 return super.onOptionsItemSelected(item);
443 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
444 Preference preference = (Preference) view.getTag();
446 if (preference instanceof LongPressAccessPointPreference) {
447 mSelectedAccessPoint =
448 ((LongPressAccessPointPreference) preference).getAccessPoint();
449 menu.setHeaderTitle(mSelectedAccessPoint.getSsid());
450 if (mSelectedAccessPoint.isConnectable()) {
451 menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
454 WifiConfiguration config = mSelectedAccessPoint.getConfig();
455 // Some configs are ineditable
456 if (isEditabilityLockedDown(getActivity(), config)) {
460 if (mSelectedAccessPoint.isSaved() || mSelectedAccessPoint.isEphemeral()) {
461 // Allow forgetting a network if either the network is saved or ephemerally
462 // connected. (In the latter case, "forget" blacklists the network so it won't
463 // be used again, ephemerally).
464 menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
466 if (mSelectedAccessPoint.isSaved()) {
467 menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
468 NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
469 if (nfcAdapter != null && nfcAdapter.isEnabled() &&
470 mSelectedAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
471 // Only allow writing of NFC tags for password-protected networks.
472 menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc);
479 public boolean onContextItemSelected(MenuItem item) {
480 if (mSelectedAccessPoint == null) {
481 return super.onContextItemSelected(item);
483 switch (item.getItemId()) {
484 case MENU_ID_CONNECT: {
485 boolean isSavedNetwork = mSelectedAccessPoint.isSaved();
486 if (isSavedNetwork) {
487 connect(mSelectedAccessPoint.getConfig(), isSavedNetwork);
488 } else if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE) {
489 /** Bypass dialog for unsecured networks */
490 mSelectedAccessPoint.generateOpenNetworkConfig();
491 connect(mSelectedAccessPoint.getConfig(), isSavedNetwork);
493 showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_CONNECT);
497 case MENU_ID_FORGET: {
501 case MENU_ID_MODIFY: {
502 showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_MODIFY);
505 case MENU_ID_WRITE_NFC:
506 showDialog(WRITE_NFC_DIALOG_ID);
510 return super.onContextItemSelected(item);
514 public boolean onPreferenceTreeClick(Preference preference) {
515 // If the preference has a fragment set, open that
516 if (preference.getFragment() != null) {
517 preference.setOnPreferenceClickListener(null);
518 return super.onPreferenceTreeClick(preference);
521 if (preference instanceof LongPressAccessPointPreference) {
522 mSelectedAccessPoint = ((LongPressAccessPointPreference) preference).getAccessPoint();
523 if (mSelectedAccessPoint == null) {
526 if (mSelectedAccessPoint.isActive()) {
527 return super.onPreferenceTreeClick(preference);
530 * Bypass dialog and connect to unsecured networks, or previously connected saved
531 * networks, or Passpoint provided networks.
533 WifiConfiguration config = mSelectedAccessPoint.getConfig();
534 if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE) {
535 mSelectedAccessPoint.generateOpenNetworkConfig();
536 connect(mSelectedAccessPoint.getConfig(), mSelectedAccessPoint.isSaved());
537 } else if (mSelectedAccessPoint.isSaved() && config != null
538 && config.getNetworkSelectionStatus() != null
539 && config.getNetworkSelectionStatus().getHasEverConnected()) {
540 connect(config, true /* isSavedNetwork */);
541 } else if (mSelectedAccessPoint.isPasspoint()) {
542 // Access point provided by an installed Passpoint provider, connect using
543 // the associated config.
544 connect(config, true /* isSavedNetwork */);
546 showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_CONNECT);
548 } else if (preference == mAddPreference) {
549 onAddNetworkPressed();
551 return super.onPreferenceTreeClick(preference);
556 private void showDialog(AccessPoint accessPoint, int dialogMode) {
557 if (accessPoint != null) {
558 WifiConfiguration config = accessPoint.getConfig();
559 if (isEditabilityLockedDown(getActivity(), config) && accessPoint.isActive()) {
560 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(),
561 RestrictedLockUtils.getDeviceOwner(getActivity()));
566 if (mDialog != null) {
567 removeDialog(WIFI_DIALOG_ID);
571 // Save the access point and edit mode
572 mDlgAccessPoint = accessPoint;
573 mDialogMode = dialogMode;
575 showDialog(WIFI_DIALOG_ID);
579 public Dialog onCreateDialog(int dialogId) {
582 AccessPoint ap = mDlgAccessPoint; // For manual launch
583 if (ap == null) { // For re-launch from saved state
584 if (mAccessPointSavedState != null) {
585 ap = new AccessPoint(getActivity(), mAccessPointSavedState);
586 // For repeated orientation changes
587 mDlgAccessPoint = ap;
588 // Reset the saved access point data
589 mAccessPointSavedState = null;
592 // If it's null, fine, it's for Add Network
593 mSelectedAccessPoint = ap;
594 mDialog = new WifiDialog(getActivity(), this, ap, mDialogMode,
595 /* no hide submit/connect */ false);
597 case WPS_PBC_DIALOG_ID:
598 return new WpsDialog(getActivity(), WpsInfo.PBC);
599 case WPS_PIN_DIALOG_ID:
600 return new WpsDialog(getActivity(), WpsInfo.DISPLAY);
601 case WRITE_NFC_DIALOG_ID:
602 if (mSelectedAccessPoint != null) {
603 mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
605 mSelectedAccessPoint.getSecurity(),
606 new WifiManagerWrapper(mWifiManager));
607 } else if (mWifiNfcDialogSavedState != null) {
608 mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(getActivity(),
609 mWifiNfcDialogSavedState, new WifiManagerWrapper(mWifiManager));
612 return mWifiToNfcDialog;
614 return super.onCreateDialog(dialogId);
618 public int getDialogMetricsCategory(int dialogId) {
621 return MetricsEvent.DIALOG_WIFI_AP_EDIT;
622 case WPS_PBC_DIALOG_ID:
623 return MetricsEvent.DIALOG_WIFI_PBC;
624 case WPS_PIN_DIALOG_ID:
625 return MetricsEvent.DIALOG_WIFI_PIN;
626 case WRITE_NFC_DIALOG_ID:
627 return MetricsEvent.DIALOG_WIFI_WRITE_NFC;
634 * Called to indicate the list of AccessPoints has been updated and
635 * getAccessPoints should be called to get the latest information.
638 public void onAccessPointsChanged() {
639 updateAccessPointsDelayed();
643 * Updates access points from {@link WifiManager#getScanResults()}. Adds a delay to have
644 * progress bar displayed before starting to modify APs.
646 private void updateAccessPointsDelayed() {
647 // Safeguard from some delayed event handling
648 if (getActivity() != null && !isUiRestricted() && mWifiManager.isWifiEnabled()) {
649 setProgressBarVisible(true);
650 getView().postDelayed(mUpdateAccessPointsRunnable, 300 /* delay milliseconds */);
654 /** Called when the state of Wifi has changed. */
656 public void onWifiStateChanged(int state) {
657 if (isUiRestricted()) {
661 final int wifiState = mWifiManager.getWifiState();
663 case WifiManager.WIFI_STATE_ENABLED:
664 updateAccessPointsDelayed();
667 case WifiManager.WIFI_STATE_ENABLING:
668 removeConnectedAccessPointPreference();
669 mAccessPointsPreferenceCategory.removeAll();
670 addMessagePreference(R.string.wifi_starting);
671 setProgressBarVisible(true);
674 case WifiManager.WIFI_STATE_DISABLING:
675 removeConnectedAccessPointPreference();
676 mAccessPointsPreferenceCategory.removeAll();
677 addMessagePreference(R.string.wifi_stopping);
680 case WifiManager.WIFI_STATE_DISABLED:
682 setProgressBarVisible(false);
688 * Called when the connection state of wifi has changed and isConnected
689 * should be called to get the updated state.
692 public void onConnectedChanged() {
693 updateAccessPointsDelayed();
694 changeNextButtonState(mWifiTracker.isConnected());
698 private void updateAccessPointPreferences() {
699 // in case state has changed
700 if (!mWifiManager.isWifiEnabled()) {
703 // AccessPoints are sorted by the WifiTracker
704 final List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints();
706 boolean hasAvailableAccessPoints = false;
707 mAccessPointsPreferenceCategory.removePreference(mStatusMessagePreference);
708 cacheRemoveAllPrefs(mAccessPointsPreferenceCategory);
711 configureConnectedAccessPointPreferenceCategory(accessPoints) ? 1 : 0;
712 int numAccessPoints = accessPoints.size();
713 for (; index < numAccessPoints; index++) {
714 AccessPoint accessPoint = accessPoints.get(index);
715 // Ignore access points that are out of range.
716 if (accessPoint.isReachable()) {
717 String key = accessPoint.getBssid();
718 if (TextUtils.isEmpty(key)) {
719 key = accessPoint.getSsidStr();
721 hasAvailableAccessPoints = true;
722 LongPressAccessPointPreference pref =
723 (LongPressAccessPointPreference) getCachedPreference(key);
725 pref.setOrder(index);
728 LongPressAccessPointPreference preference =
729 createLongPressActionPointPreference(accessPoint);
730 preference.setKey(key);
731 preference.setOrder(index);
732 if (mOpenSsid != null && mOpenSsid.equals(accessPoint.getSsidStr())
733 && !accessPoint.isSaved()
734 && accessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
735 onPreferenceTreeClick(preference);
738 mAccessPointsPreferenceCategory.addPreference(preference);
739 accessPoint.setListener(WifiSettings.this);
740 preference.refresh();
743 removeCachedPrefs(mAccessPointsPreferenceCategory);
744 mAddPreference.setOrder(index);
745 mAccessPointsPreferenceCategory.addPreference(mAddPreference);
746 setAdditionalSettingsSummaries();
748 if (!hasAvailableAccessPoints) {
749 setProgressBarVisible(true);
750 Preference pref = new Preference(getPrefContext());
751 pref.setSelectable(false);
752 pref.setSummary(R.string.wifi_empty_list_wifi_on);
753 pref.setOrder(index++);
754 pref.setKey(PREF_KEY_EMPTY_WIFI_LIST);
755 mAccessPointsPreferenceCategory.addPreference(pref);
757 // Continuing showing progress bar for an additional delay to overlap with animation
758 getView().postDelayed(mHideProgressBarRunnable, 1700 /* delay millis */);
763 private LongPressAccessPointPreference createLongPressActionPointPreference(
764 AccessPoint accessPoint) {
765 return new LongPressAccessPointPreference(accessPoint, getPrefContext(), mUserBadgeCache,
766 false, R.drawable.ic_wifi_signal_0, this);
770 * Configure the ConnectedAccessPointPreferenceCategory and return true if the Category was
773 private boolean configureConnectedAccessPointPreferenceCategory(
774 List<AccessPoint> accessPoints) {
775 if (accessPoints.size() == 0) {
776 removeConnectedAccessPointPreference();
780 AccessPoint connectedAp = accessPoints.get(0);
781 if (!connectedAp.isActive()) {
782 removeConnectedAccessPointPreference();
786 // Is the preference category empty?
787 if (mConnectedAccessPointPreferenceCategory.getPreferenceCount() == 0) {
788 addConnectedAccessPointPreference(connectedAp);
792 // Is the previous currently connected SSID different from the new one?
793 if (!((AccessPointPreference)
794 mConnectedAccessPointPreferenceCategory.getPreference(0))
795 .getAccessPoint().getSsidStr().equals(
796 connectedAp.getSsidStr())) {
797 removeConnectedAccessPointPreference();
798 addConnectedAccessPointPreference(connectedAp);
802 // Else same AP is connected, simply refresh the connected access point preference
803 // (first and only access point in this category).
804 ((LongPressAccessPointPreference) mConnectedAccessPointPreferenceCategory.getPreference(0))
810 * Creates a Preference for the given {@link AccessPoint} and adds it to the
811 * {@link #mConnectedAccessPointPreferenceCategory}.
813 private void addConnectedAccessPointPreference(AccessPoint connectedAp) {
814 String key = connectedAp.getBssid();
815 LongPressAccessPointPreference pref = (LongPressAccessPointPreference)
816 getCachedPreference(key);
818 pref = createLongPressActionPointPreference(connectedAp);
821 // Save the state of the current access point in the bundle so that we can restore it
822 // in the Wifi Network Details Fragment
823 pref.getAccessPoint().saveWifiState(pref.getExtras());
824 pref.setFragment(WifiNetworkDetailsFragment.class.getName());
827 mConnectedAccessPointPreferenceCategory.addPreference(pref);
828 mConnectedAccessPointPreferenceCategory.setVisible(true);
831 /** Removes all preferences and hide the {@link #mConnectedAccessPointPreferenceCategory}. */
832 private void removeConnectedAccessPointPreference() {
833 mConnectedAccessPointPreferenceCategory.removeAll();
834 mConnectedAccessPointPreferenceCategory.setVisible(false);
837 private void setAdditionalSettingsSummaries() {
838 mAdditionalSettingsPreferenceCategory.addPreference(mConfigureWifiSettingsPreference);
839 boolean wifiWakeupEnabled = Settings.Global.getInt(
840 getContentResolver(), Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1;
841 mConfigureWifiSettingsPreference.setSummary(getString(wifiWakeupEnabled
842 ? R.string.wifi_configure_settings_preference_summary_wakeup_on
843 : R.string.wifi_configure_settings_preference_summary_wakeup_off));
844 int numSavedNetworks = mWifiTracker.getNumSavedNetworks();
845 if (numSavedNetworks > 0) {
846 mAdditionalSettingsPreferenceCategory.addPreference(mSavedNetworksPreference);
847 mSavedNetworksPreference.setSummary(
848 getResources().getQuantityString(R.plurals.wifi_saved_access_points_summary,
849 numSavedNetworks, numSavedNetworks));
851 mAdditionalSettingsPreferenceCategory.removePreference(mSavedNetworksPreference);
855 private void setOffMessage() {
856 final CharSequence title = getText(R.string.wifi_empty_list_wifi_off);
857 // Don't use WifiManager.isScanAlwaysAvailable() to check the Wi-Fi scanning mode. Instead,
858 // read the system settings directly. Because when the device is in Airplane mode, even if
859 // Wi-Fi scanning mode is on, WifiManager.isScanAlwaysAvailable() still returns "off".
860 final boolean wifiScanningMode = Settings.Global.getInt(getActivity().getContentResolver(),
861 Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1;
862 final CharSequence description = wifiScanningMode ? getText(R.string.wifi_scan_notify_text)
863 : getText(R.string.wifi_scan_notify_text_scanning_off);
864 final LinkifyUtils.OnClickListener clickListener = new LinkifyUtils.OnClickListener() {
866 public void onClick() {
867 final SettingsActivity activity = (SettingsActivity) getActivity();
868 activity.startPreferencePanel(WifiSettings.this,
869 ScanningSettings.class.getName(),
870 null, R.string.location_scanning_screen_title, null, null, 0);
873 mStatusMessagePreference.setText(title, description, clickListener);
874 removeConnectedAccessPointPreference();
875 mAccessPointsPreferenceCategory.removeAll();
876 mAccessPointsPreferenceCategory.addPreference(mStatusMessagePreference);
879 private void addMessagePreference(int messageId) {
880 mStatusMessagePreference.setTitle(messageId);
881 removeConnectedAccessPointPreference();
882 mAccessPointsPreferenceCategory.removeAll();
883 mAccessPointsPreferenceCategory.addPreference(mStatusMessagePreference);
886 protected void setProgressBarVisible(boolean visible) {
887 if (mProgressHeader != null) {
888 mProgressHeader.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
893 * Renames/replaces "Next" button when appropriate. "Next" button usually exists in
894 * Wifi setup screens, not in usual wifi settings screen.
896 * @param enabled true when the device is connected to a wifi network.
898 private void changeNextButtonState(boolean enabled) {
899 if (mEnableNextOnConnection && hasNextButton()) {
900 getNextButton().setEnabled(enabled);
905 public void onForget(WifiDialog dialog) {
910 public void onSubmit(WifiDialog dialog) {
911 if (mDialog != null) {
912 submit(mDialog.getController());
916 /* package */ void submit(WifiConfigController configController) {
918 final WifiConfiguration config = configController.getConfig();
920 if (config == null) {
921 if (mSelectedAccessPoint != null
922 && mSelectedAccessPoint.isSaved()) {
923 connect(mSelectedAccessPoint.getConfig(), true /* isSavedNetwork */);
925 } else if (configController.getMode() == WifiConfigUiBase.MODE_MODIFY) {
926 mWifiManager.save(config, mSaveListener);
928 mWifiManager.save(config, mSaveListener);
929 if (mSelectedAccessPoint != null) { // Not an "Add network"
930 connect(config, false /* isSavedNetwork */);
934 mWifiTracker.resumeScanning();
937 /* package */ void forget() {
938 mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_FORGET);
939 if (!mSelectedAccessPoint.isSaved()) {
940 if (mSelectedAccessPoint.getNetworkInfo() != null &&
941 mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED) {
942 // Network is active but has no network ID - must be ephemeral.
943 mWifiManager.disableEphemeralNetwork(
944 AccessPoint.convertToQuotedString(mSelectedAccessPoint.getSsidStr()));
946 // Should not happen, but a monkey seems to trigger it
947 Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig());
950 } else if (mSelectedAccessPoint.getConfig().isPasspoint()) {
951 mWifiManager.removePasspointConfiguration(mSelectedAccessPoint.getConfig().FQDN);
953 mWifiManager.forget(mSelectedAccessPoint.getConfig().networkId, mForgetListener);
956 mWifiTracker.resumeScanning();
958 // We need to rename/replace "Next" button in wifi setup context.
959 changeNextButtonState(false);
962 protected void connect(final WifiConfiguration config, boolean isSavedNetwork) {
963 // Log subtype if configuration is a saved network.
964 mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT,
966 mWifiManager.connect(config, mConnectListener);
969 protected void connect(final int networkId, boolean isSavedNetwork) {
970 // Log subtype if configuration is a saved network.
971 mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT,
973 mWifiManager.connect(networkId, mConnectListener);
977 * Called when "add network" button is pressed.
979 /* package */ void onAddNetworkPressed() {
980 mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_ADD_NETWORK);
981 // No exact access point is selected.
982 mSelectedAccessPoint = null;
983 showDialog(null, WifiConfigUiBase.MODE_CONNECT);
987 protected int getHelpResource() {
988 return R.string.help_url_wifi;
992 public void onAccessPointChanged(final AccessPoint accessPoint) {
993 View view = getView();
995 view.post(new Runnable() {
998 Object tag = accessPoint.getTag();
1000 ((LongPressAccessPointPreference) tag).refresh();
1008 public void onLevelChanged(AccessPoint accessPoint) {
1009 ((LongPressAccessPointPreference) accessPoint.getTag()).onLevelChanged();
1012 public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
1013 new BaseSearchIndexProvider() {
1015 public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
1016 final List<SearchIndexableRaw> result = new ArrayList<>();
1017 final Resources res = context.getResources();
1019 // Add fragment title
1020 SearchIndexableRaw data = new SearchIndexableRaw(context);
1021 data.title = res.getString(R.string.wifi_settings);
1022 data.screenTitle = res.getString(R.string.wifi_settings);
1023 data.keywords = res.getString(R.string.keywords_wifi);
1024 data.key = DATA_KEY_REFERENCE;
1027 // Add saved Wi-Fi access points
1028 final List<AccessPoint> accessPoints =
1029 WifiTracker.getCurrentAccessPoints(context, true, false, false);
1030 for (AccessPoint accessPoint : accessPoints) {
1031 data = new SearchIndexableRaw(context);
1032 data.title = accessPoint.getSsidStr();
1033 data.screenTitle = res.getString(R.string.wifi_settings);
1034 data.enabled = enabled;
1043 * Returns true if the config is not editable through Settings.
1044 * @param context Context of caller
1045 * @param config The WiFi config.
1046 * @return true if the config is not editable through Settings.
1048 static boolean isEditabilityLockedDown(Context context, WifiConfiguration config) {
1049 return !canModifyNetwork(context, config);
1053 * This method is a stripped version of WifiConfigStore.canModifyNetwork.
1054 * TODO: refactor to have only one method.
1055 * @param context Context of caller
1056 * @param config The WiFi config.
1057 * @return true if Settings can modify the config.
1059 static boolean canModifyNetwork(Context context, WifiConfiguration config) {
1060 if (config == null) {
1064 final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
1065 Context.DEVICE_POLICY_SERVICE);
1067 // Check if device has DPM capability. If it has and dpm is still null, then we
1068 // treat this case with suspicion and bail out.
1069 final PackageManager pm = context.getPackageManager();
1070 if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) && dpm == null) {
1074 boolean isConfigEligibleForLockdown = false;
1076 final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnAnyUser();
1077 if (deviceOwner != null) {
1078 final int deviceOwnerUserId = dpm.getDeviceOwnerUserId();
1080 final int deviceOwnerUid = pm.getPackageUidAsUser(deviceOwner.getPackageName(),
1082 isConfigEligibleForLockdown = deviceOwnerUid == config.creatorUid;
1083 } catch (NameNotFoundException e) {
1088 if (!isConfigEligibleForLockdown) {
1092 final ContentResolver resolver = context.getContentResolver();
1093 final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
1094 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
1095 return !isLockdownFeatureEnabled;
1098 private static class SummaryProvider
1099 implements SummaryLoader.SummaryProvider, OnSummaryChangeListener {
1101 private final Context mContext;
1102 private final SummaryLoader mSummaryLoader;
1105 WifiSummaryUpdater mSummaryHelper;
1107 public SummaryProvider(Context context, SummaryLoader summaryLoader) {
1109 mSummaryLoader = summaryLoader;
1110 mSummaryHelper = new WifiSummaryUpdater(mContext, this);
1115 public void setListening(boolean listening) {
1116 mSummaryHelper.register(listening);
1120 public void onSummaryChanged(String summary) {
1121 mSummaryLoader.setSummary(this, summary);
1125 public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
1126 = new SummaryLoader.SummaryProviderFactory() {
1128 public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
1129 SummaryLoader summaryLoader) {
1130 return new SummaryProvider(activity, summaryLoader);