WifiSettings.java revision 87d120a7e1571b8f7743c676cdf7a0d2cd82e7b1
1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
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
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
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.
15 */
16
17package com.android.settings.wifi;
18
19import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
20
21import android.annotation.NonNull;
22import android.app.Activity;
23import android.app.Dialog;
24import android.content.ContentResolver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.res.Resources;
28import android.net.ConnectivityManager;
29import android.net.NetworkInfo;
30import android.net.NetworkInfo.State;
31import android.net.wifi.WifiConfiguration;
32import android.net.wifi.WifiManager;
33import android.net.wifi.WpsInfo;
34import android.nfc.NfcAdapter;
35import android.os.Bundle;
36import android.os.PowerManager;
37import android.provider.Settings;
38import android.support.annotation.VisibleForTesting;
39import android.support.v7.preference.Preference;
40import android.support.v7.preference.PreferenceCategory;
41import android.util.Log;
42import android.view.ContextMenu;
43import android.view.ContextMenu.ContextMenuInfo;
44import android.view.Menu;
45import android.view.MenuItem;
46import android.view.View;
47import android.widget.Toast;
48
49import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
50import com.android.settings.LinkifyUtils;
51import com.android.settings.R;
52import com.android.settings.RestrictedSettingsFragment;
53import com.android.settings.SettingsActivity;
54import com.android.settings.dashboard.SummaryLoader;
55import com.android.settings.location.ScanningSettings;
56import com.android.settings.search.BaseSearchIndexProvider;
57import com.android.settings.search.Indexable;
58import com.android.settings.search.SearchIndexableRaw;
59import com.android.settings.widget.SummaryUpdater.OnSummaryChangeListener;
60import com.android.settings.widget.SwitchBarController;
61import com.android.settings.wifi.details.WifiNetworkDetailsFragment;
62import com.android.settings.wrapper.WifiManagerWrapper;
63import com.android.settingslib.RestrictedLockUtils;
64import com.android.settingslib.wifi.AccessPoint;
65import com.android.settingslib.wifi.AccessPoint.AccessPointListener;
66import com.android.settingslib.wifi.AccessPointPreference;
67import com.android.settingslib.wifi.WifiTracker;
68import com.android.settingslib.wifi.WifiTrackerFactory;
69
70import java.util.ArrayList;
71import java.util.List;
72
73/**
74 * Two types of UI are provided here.
75 *
76 * The first is for "usual Settings", appearing as any other Setup fragment.
77 *
78 * The second is for Setup Wizard, with a simplified interface that hides the action bar
79 * and menus.
80 */
81public class WifiSettings extends RestrictedSettingsFragment
82        implements Indexable, WifiTracker.WifiListener, AccessPointListener,
83        WifiDialog.WifiDialogListener {
84
85    private static final String TAG = "WifiSettings";
86
87    /* package */ static final int MENU_ID_WPS_PBC = Menu.FIRST;
88    private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1;
89    private static final int MENU_ID_CONNECT = Menu.FIRST + 6;
90    private static final int MENU_ID_FORGET = Menu.FIRST + 7;
91    private static final int MENU_ID_MODIFY = Menu.FIRST + 8;
92    private static final int MENU_ID_WRITE_NFC = Menu.FIRST + 9;
93
94    public static final int WIFI_DIALOG_ID = 1;
95    /* package */ static final int WPS_PBC_DIALOG_ID = 2;
96    private static final int WPS_PIN_DIALOG_ID = 3;
97    private static final int WRITE_NFC_DIALOG_ID = 6;
98
99    // Instance state keys
100    private static final String SAVE_DIALOG_MODE = "dialog_mode";
101    private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
102    private static final String SAVED_WIFI_NFC_DIALOG_STATE = "wifi_nfc_dlg_state";
103
104    private static final String PREF_KEY_EMPTY_WIFI_LIST = "wifi_empty_list";
105    private static final String PREF_KEY_CONNECTED_ACCESS_POINTS = "connected_access_point";
106    private static final String PREF_KEY_ACCESS_POINTS = "access_points";
107    private static final String PREF_KEY_ADDITIONAL_SETTINGS = "additional_settings";
108    private static final String PREF_KEY_CONFIGURE_WIFI_SETTINGS = "configure_settings";
109    private static final String PREF_KEY_SAVED_NETWORKS = "saved_networks";
110
111    private final Runnable mUpdateAccessPointsRunnable = () -> {
112        updateAccessPointPreferences();
113    };
114    private final Runnable mHideProgressBarRunnable = () -> {
115        setProgressBarVisible(false);
116    };
117
118    protected WifiManager mWifiManager;
119    private WifiManager.ActionListener mConnectListener;
120    private WifiManager.ActionListener mSaveListener;
121    private WifiManager.ActionListener mForgetListener;
122
123    /**
124     * The state of {@link #isUiRestricted()} at {@link #onCreate(Bundle)}}. This is neccesary to
125     * ensure that behavior is consistent if {@link #isUiRestricted()} changes. It could be changed
126     * by the Test DPC tool in AFW mode.
127     */
128    private boolean mIsRestricted;
129
130    private WifiEnabler mWifiEnabler;
131    // An access point being editted is stored here.
132    private AccessPoint mSelectedAccessPoint;
133
134    private WifiDialog mDialog;
135    private WriteWifiConfigToNfcDialog mWifiToNfcDialog;
136
137    private View mProgressHeader;
138
139    // this boolean extra specifies whether to disable the Next button when not connected. Used by
140    // account creation outside of setup wizard.
141    private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
142    // This string extra specifies a network to open the connect dialog on, so the user can enter
143    // network credentials.  This is used by quick settings for secured networks, among other
144    // things.
145    public static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
146
147    // should Next button only be enabled when we have a connection?
148    private boolean mEnableNextOnConnection;
149
150    // Save the dialog details
151    private int mDialogMode;
152    private AccessPoint mDlgAccessPoint;
153    private Bundle mAccessPointSavedState;
154    private Bundle mWifiNfcDialogSavedState;
155
156    private WifiTracker mWifiTracker;
157    private String mOpenSsid;
158
159    private AccessPointPreference.UserBadgeCache mUserBadgeCache;
160
161    private PreferenceCategory mConnectedAccessPointPreferenceCategory;
162    private PreferenceCategory mAccessPointsPreferenceCategory;
163    private PreferenceCategory mAdditionalSettingsPreferenceCategory;
164    private Preference mAddPreference;
165    private Preference mConfigureWifiSettingsPreference;
166    private Preference mSavedNetworksPreference;
167    private LinkablePreference mStatusMessagePreference;
168
169    // For Search
170    public static final String DATA_KEY_REFERENCE = "main_toggle_wifi";
171
172    /**
173     * Tracks whether the user initiated a connection via clicking in order to autoscroll to the
174     * network once connected.
175     */
176    private boolean mClickedConnect;
177
178    /* End of "used in Wifi Setup context" */
179
180    public WifiSettings() {
181        super(DISALLOW_CONFIG_WIFI);
182    }
183
184    @Override
185    public void onViewCreated(View view, Bundle savedInstanceState) {
186        super.onViewCreated(view, savedInstanceState);
187        final Activity activity = getActivity();
188        if (activity != null) {
189            mProgressHeader = setPinnedHeaderView(R.layout.wifi_progress_header)
190                    .findViewById(R.id.progress_bar_animation);
191            setProgressBarVisible(false);
192        }
193        ((SettingsActivity) activity).getSwitchBar().setSwitchBarText(
194                R.string.wifi_settings_master_switch_title,
195                R.string.wifi_settings_master_switch_title);
196    }
197
198    @Override
199    public void onCreate(Bundle icicle) {
200        super.onCreate(icicle);
201
202        // TODO(b/37429702): Add animations and preference comparator back after initial screen is
203        // loaded (ODR).
204        setAnimationAllowed(false);
205
206        addPreferences();
207
208        mIsRestricted = isUiRestricted();
209    }
210
211    private void addPreferences() {
212        addPreferencesFromResource(R.xml.wifi_settings);
213
214        mConnectedAccessPointPreferenceCategory =
215                (PreferenceCategory) findPreference(PREF_KEY_CONNECTED_ACCESS_POINTS);
216        mAccessPointsPreferenceCategory =
217                (PreferenceCategory) findPreference(PREF_KEY_ACCESS_POINTS);
218        mAdditionalSettingsPreferenceCategory =
219                (PreferenceCategory) findPreference(PREF_KEY_ADDITIONAL_SETTINGS);
220        mConfigureWifiSettingsPreference = findPreference(PREF_KEY_CONFIGURE_WIFI_SETTINGS);
221        mSavedNetworksPreference = findPreference(PREF_KEY_SAVED_NETWORKS);
222
223        Context prefContext = getPrefContext();
224        mAddPreference = new Preference(prefContext);
225        mAddPreference.setIcon(R.drawable.ic_menu_add_inset);
226        mAddPreference.setTitle(R.string.wifi_add_network);
227        mStatusMessagePreference = new LinkablePreference(prefContext);
228
229        mUserBadgeCache = new AccessPointPreference.UserBadgeCache(getPackageManager());
230    }
231
232    @Override
233    public void onActivityCreated(Bundle savedInstanceState) {
234        super.onActivityCreated(savedInstanceState);
235
236        mWifiTracker = WifiTrackerFactory.create(
237                getActivity(), this, getLifecycle(), true, true);
238        mWifiManager = mWifiTracker.getManager();
239
240        mConnectListener = new WifiManager.ActionListener() {
241                                   @Override
242                                   public void onSuccess() {
243                                   }
244                                   @Override
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();
251                                       }
252                                   }
253                               };
254
255        mSaveListener = new WifiManager.ActionListener() {
256                                @Override
257                                public void onSuccess() {
258                                }
259                                @Override
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();
266                                    }
267                                }
268                            };
269
270        mForgetListener = new WifiManager.ActionListener() {
271                                   @Override
272                                   public void onSuccess() {
273                                   }
274                                   @Override
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();
281                                       }
282                                   }
283                               };
284
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);
290            }
291
292            if (savedInstanceState.containsKey(SAVED_WIFI_NFC_DIALOG_STATE)) {
293                mWifiNfcDialogSavedState =
294                    savedInstanceState.getBundle(SAVED_WIFI_NFC_DIALOG_STATE);
295            }
296        }
297
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);
302
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());
311                }
312            }
313        }
314
315        registerForContextMenu(getListView());
316        setHasOptionsMenu(true);
317
318        if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) {
319            mOpenSsid = intent.getStringExtra(EXTRA_START_CONNECT_SSID);
320        }
321    }
322
323    @Override
324    public void onDestroyView() {
325        super.onDestroyView();
326
327        if (mWifiEnabler != null) {
328            mWifiEnabler.teardownSwitchController();
329        }
330    }
331
332    @Override
333    public void onStart() {
334        super.onStart();
335
336        // On/off switch is hidden for Setup Wizard (returns null)
337        mWifiEnabler = createWifiEnabler();
338
339        if (mIsRestricted) {
340            restrictUi();
341            return;
342        }
343
344        onWifiStateChanged(mWifiManager.getWifiState());
345    }
346
347    private void restrictUi() {
348        if (!isUiRestrictedByOnlyAdmin()) {
349            getEmptyTextView().setText(R.string.wifi_empty_list_user_restricted);
350        }
351        getPreferenceScreen().removeAll();
352    }
353
354    /**
355     * Only update the AP list if there are not any APs currently shown.
356     *
357     * <p>Thus forceUpdate will only be called during cold start or when toggling between wifi on
358     * and off. In other use cases, the previous APs will remain until the next update is received
359     * from {@link WifiTracker}.
360     */
361    private void conditionallyForceUpdateAPs() {
362        if (mAccessPointsPreferenceCategory.getPreferenceCount() > 0
363                && mAccessPointsPreferenceCategory.getPreference(0) instanceof
364                        AccessPointPreference) {
365            // Make sure we don't update due to callbacks initiated by sticky broadcasts in
366            // WifiTracker.
367            Log.d(TAG, "Did not force update APs due to existing APs displayed");
368            getView().removeCallbacks(mUpdateAccessPointsRunnable);
369            return;
370        }
371        setProgressBarVisible(true);
372        mWifiTracker.forceUpdate();
373        if (WifiTracker.sVerboseLogging) {
374            Log.i(TAG, "WifiSettings force update APs: " + mWifiTracker.getAccessPoints());
375        }
376        getView().removeCallbacks(mUpdateAccessPointsRunnable);
377        updateAccessPointPreferences();
378    }
379
380    /**
381     * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard)
382     */
383    private WifiEnabler createWifiEnabler() {
384        final SettingsActivity activity = (SettingsActivity) getActivity();
385        return new WifiEnabler(activity, new SwitchBarController(activity.getSwitchBar()),
386            mMetricsFeatureProvider);
387    }
388
389    @Override
390    public void onResume() {
391        final Activity activity = getActivity();
392        super.onResume();
393
394        // Because RestrictedSettingsFragment's onResume potentially requests authorization,
395        // which changes the restriction state, recalculate it.
396        final boolean alreadyImmutablyRestricted = mIsRestricted;
397        mIsRestricted = isUiRestricted();
398        if (!alreadyImmutablyRestricted && mIsRestricted) {
399            restrictUi();
400        }
401
402        if (mWifiEnabler != null) {
403            mWifiEnabler.resume(activity);
404        }
405    }
406
407    @Override
408    public void onPause() {
409        super.onPause();
410        if (mWifiEnabler != null) {
411            mWifiEnabler.pause();
412        }
413    }
414
415    @Override
416    public void onStop() {
417        getView().removeCallbacks(mUpdateAccessPointsRunnable);
418        getView().removeCallbacks(mHideProgressBarRunnable);
419        super.onStop();
420    }
421
422    @Override
423    public void onActivityResult(int requestCode, int resultCode, Intent data) {
424        super.onActivityResult(requestCode, resultCode, data);
425
426        final boolean formerlyRestricted = mIsRestricted;
427        mIsRestricted = isUiRestricted();
428        if (formerlyRestricted && !mIsRestricted
429                && getPreferenceScreen().getPreferenceCount() == 0) {
430            // De-restrict the ui
431            addPreferences();
432        }
433    }
434
435    @Override
436    public int getMetricsCategory() {
437        return MetricsEvent.WIFI;
438    }
439
440    @Override
441    public void onSaveInstanceState(Bundle outState) {
442        super.onSaveInstanceState(outState);
443
444        // If the dialog is showing, save its state.
445        if (mDialog != null && mDialog.isShowing()) {
446            outState.putInt(SAVE_DIALOG_MODE, mDialogMode);
447            if (mDlgAccessPoint != null) {
448                mAccessPointSavedState = new Bundle();
449                mDlgAccessPoint.saveWifiState(mAccessPointSavedState);
450                outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
451            }
452        }
453
454        if (mWifiToNfcDialog != null && mWifiToNfcDialog.isShowing()) {
455            Bundle savedState = new Bundle();
456            mWifiToNfcDialog.saveState(savedState);
457            outState.putBundle(SAVED_WIFI_NFC_DIALOG_STATE, savedState);
458        }
459    }
460
461    @Override
462    public boolean onOptionsItemSelected(MenuItem item) {
463        // If the user is not allowed to configure wifi, do not handle menu selections.
464        if (mIsRestricted) {
465            return false;
466        }
467
468        switch (item.getItemId()) {
469            case MENU_ID_WPS_PBC:
470                showDialog(WPS_PBC_DIALOG_ID);
471                return true;
472            case MENU_ID_WPS_PIN:
473                showDialog(WPS_PIN_DIALOG_ID);
474                return true;
475        }
476        return super.onOptionsItemSelected(item);
477    }
478
479    @Override
480    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
481            Preference preference = (Preference) view.getTag();
482
483            if (preference instanceof LongPressAccessPointPreference) {
484                mSelectedAccessPoint =
485                        ((LongPressAccessPointPreference) preference).getAccessPoint();
486                menu.setHeaderTitle(mSelectedAccessPoint.getSsid());
487                if (mSelectedAccessPoint.isConnectable()) {
488                    menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
489                }
490
491                WifiConfiguration config = mSelectedAccessPoint.getConfig();
492                // Some configs are ineditable
493                if (WifiUtils.isNetworkLockedDown(getActivity(), config)) {
494                    return;
495                }
496
497                if (mSelectedAccessPoint.isSaved() || mSelectedAccessPoint.isEphemeral()) {
498                    // Allow forgetting a network if either the network is saved or ephemerally
499                    // connected. (In the latter case, "forget" blacklists the network so it won't
500                    // be used again, ephemerally).
501                    menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
502                }
503                if (mSelectedAccessPoint.isSaved()) {
504                    menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
505                    NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
506                    if (nfcAdapter != null && nfcAdapter.isEnabled() &&
507                            mSelectedAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
508                        // Only allow writing of NFC tags for password-protected networks.
509                        menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc);
510                    }
511                }
512            }
513    }
514
515    @Override
516    public boolean onContextItemSelected(MenuItem item) {
517        if (mSelectedAccessPoint == null) {
518            return super.onContextItemSelected(item);
519        }
520        switch (item.getItemId()) {
521            case MENU_ID_CONNECT: {
522                boolean isSavedNetwork = mSelectedAccessPoint.isSaved();
523                if (isSavedNetwork) {
524                    connect(mSelectedAccessPoint.getConfig(), isSavedNetwork);
525                } else if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE) {
526                    /** Bypass dialog for unsecured networks */
527                    mSelectedAccessPoint.generateOpenNetworkConfig();
528                    connect(mSelectedAccessPoint.getConfig(), isSavedNetwork);
529                } else {
530                    showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_CONNECT);
531                }
532                return true;
533            }
534            case MENU_ID_FORGET: {
535                forget();
536                return true;
537            }
538            case MENU_ID_MODIFY: {
539                showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_MODIFY);
540                return true;
541            }
542            case MENU_ID_WRITE_NFC:
543                showDialog(WRITE_NFC_DIALOG_ID);
544                return true;
545
546        }
547        return super.onContextItemSelected(item);
548    }
549
550    @Override
551    public boolean onPreferenceTreeClick(Preference preference) {
552        // If the preference has a fragment set, open that
553        if (preference.getFragment() != null) {
554            preference.setOnPreferenceClickListener(null);
555            return super.onPreferenceTreeClick(preference);
556        }
557
558        if (preference instanceof LongPressAccessPointPreference) {
559            mSelectedAccessPoint = ((LongPressAccessPointPreference) preference).getAccessPoint();
560            if (mSelectedAccessPoint == null) {
561                return false;
562            }
563            if (mSelectedAccessPoint.isActive()) {
564                return super.onPreferenceTreeClick(preference);
565            }
566            /**
567             * Bypass dialog and connect to unsecured networks, or previously connected saved
568             * networks, or Passpoint provided networks.
569             */
570            WifiConfiguration config = mSelectedAccessPoint.getConfig();
571            if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE) {
572                mSelectedAccessPoint.generateOpenNetworkConfig();
573                connect(mSelectedAccessPoint.getConfig(), mSelectedAccessPoint.isSaved());
574            } else if (mSelectedAccessPoint.isSaved() && config != null
575                    && config.getNetworkSelectionStatus() != null
576                    && config.getNetworkSelectionStatus().getHasEverConnected()) {
577                connect(config, true /* isSavedNetwork */);
578            } else if (mSelectedAccessPoint.isPasspoint()) {
579                // Access point provided by an installed Passpoint provider, connect using
580                // the associated config.
581                connect(config, true /* isSavedNetwork */);
582            } else {
583                showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_CONNECT);
584            }
585        } else if (preference == mAddPreference) {
586            onAddNetworkPressed();
587        } else {
588            return super.onPreferenceTreeClick(preference);
589        }
590        return true;
591    }
592
593    private void showDialog(AccessPoint accessPoint, int dialogMode) {
594        if (accessPoint != null) {
595            WifiConfiguration config = accessPoint.getConfig();
596            if (WifiUtils.isNetworkLockedDown(getActivity(), config) && accessPoint.isActive()) {
597                RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(),
598                        RestrictedLockUtils.getDeviceOwner(getActivity()));
599                return;
600            }
601        }
602
603        if (mDialog != null) {
604            removeDialog(WIFI_DIALOG_ID);
605            mDialog = null;
606        }
607
608        // Save the access point and edit mode
609        mDlgAccessPoint = accessPoint;
610        mDialogMode = dialogMode;
611
612        showDialog(WIFI_DIALOG_ID);
613    }
614
615    @Override
616    public Dialog onCreateDialog(int dialogId) {
617        switch (dialogId) {
618            case WIFI_DIALOG_ID:
619                if (mDlgAccessPoint == null && mAccessPointSavedState == null) {
620                    // add new network
621                    mDialog = WifiDialog
622                            .createFullscreen(getActivity(), this, mDlgAccessPoint, mDialogMode);
623                } else {
624                    // modify network
625                    if (mDlgAccessPoint == null) {
626                        // restore AP from save state
627                        mDlgAccessPoint = new AccessPoint(getActivity(), mAccessPointSavedState);
628                        // Reset the saved access point data
629                        mAccessPointSavedState = null;
630                    }
631                    mDialog = WifiDialog
632                            .createModal(getActivity(), this, mDlgAccessPoint, mDialogMode);
633                }
634
635                mSelectedAccessPoint = mDlgAccessPoint;
636                return mDialog;
637            case WPS_PBC_DIALOG_ID:
638                return new WpsDialog(getActivity(), WpsInfo.PBC);
639            case WPS_PIN_DIALOG_ID:
640                return new WpsDialog(getActivity(), WpsInfo.DISPLAY);
641            case WRITE_NFC_DIALOG_ID:
642                if (mSelectedAccessPoint != null) {
643                    mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
644                            getActivity(),
645                            mSelectedAccessPoint.getSecurity(),
646                            new WifiManagerWrapper(mWifiManager));
647                } else if (mWifiNfcDialogSavedState != null) {
648                    mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(getActivity(),
649                            mWifiNfcDialogSavedState, new WifiManagerWrapper(mWifiManager));
650                }
651
652                return mWifiToNfcDialog;
653        }
654        return super.onCreateDialog(dialogId);
655    }
656
657    @Override
658    public int getDialogMetricsCategory(int dialogId) {
659        switch (dialogId) {
660            case WIFI_DIALOG_ID:
661                return MetricsEvent.DIALOG_WIFI_AP_EDIT;
662            case WPS_PBC_DIALOG_ID:
663                return MetricsEvent.DIALOG_WIFI_PBC;
664            case WPS_PIN_DIALOG_ID:
665                return MetricsEvent.DIALOG_WIFI_PIN;
666            case WRITE_NFC_DIALOG_ID:
667                return MetricsEvent.DIALOG_WIFI_WRITE_NFC;
668            default:
669                return 0;
670        }
671    }
672
673    /**
674     * Called to indicate the list of AccessPoints has been updated and
675     * getAccessPoints should be called to get the latest information.
676     */
677    @Override
678    public void onAccessPointsChanged() {
679        Log.d(TAG, "onAccessPointsChanged (WifiTracker) callback initiated");
680        updateAccessPointsDelayed();
681    }
682
683    /**
684     * Updates access points from {@link WifiManager#getScanResults()}. Adds a delay to have
685     * progress bar displayed before starting to modify APs.
686     */
687    private void updateAccessPointsDelayed() {
688        // Safeguard from some delayed event handling
689        if (getActivity() != null && !mIsRestricted && mWifiManager.isWifiEnabled()) {
690            setProgressBarVisible(true);
691            getView().postDelayed(mUpdateAccessPointsRunnable, 300 /* delay milliseconds */);
692        }
693    }
694
695    /** Called when the state of Wifi has changed. */
696    @Override
697    public void onWifiStateChanged(int state) {
698        if (mIsRestricted) {
699            return;
700        }
701
702        final int wifiState = mWifiManager.getWifiState();
703        switch (wifiState) {
704            case WifiManager.WIFI_STATE_ENABLED:
705                conditionallyForceUpdateAPs();
706                break;
707
708            case WifiManager.WIFI_STATE_ENABLING:
709                removeConnectedAccessPointPreference();
710                mAccessPointsPreferenceCategory.removeAll();
711                addMessagePreference(R.string.wifi_starting);
712                setProgressBarVisible(true);
713                break;
714
715            case WifiManager.WIFI_STATE_DISABLING:
716                removeConnectedAccessPointPreference();
717                mAccessPointsPreferenceCategory.removeAll();
718                addMessagePreference(R.string.wifi_stopping);
719                break;
720
721            case WifiManager.WIFI_STATE_DISABLED:
722                setOffMessage();
723                setAdditionalSettingsSummaries();
724                setProgressBarVisible(false);
725                break;
726        }
727    }
728
729    /**
730     * Called when the connection state of wifi has changed.
731     */
732    @Override
733    public void onConnectedChanged() {
734        changeNextButtonState(mWifiTracker.isConnected());
735    }
736
737    /** Helper method to return whether an AccessPoint is disabled due to a wrong password */
738    private static boolean isDisabledByWrongPassword(AccessPoint accessPoint) {
739        WifiConfiguration config = accessPoint.getConfig();
740        if (config == null) {
741            return false;
742        }
743        WifiConfiguration.NetworkSelectionStatus networkStatus =
744                config.getNetworkSelectionStatus();
745        if (networkStatus == null || networkStatus.isNetworkEnabled()) {
746            return false;
747        }
748        int reason = networkStatus.getNetworkSelectionDisableReason();
749        return WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD == reason;
750    }
751
752    private void updateAccessPointPreferences() {
753        // in case state has changed
754        if (!mWifiManager.isWifiEnabled()) {
755            return;
756        }
757        // AccessPoints are sorted by the WifiTracker
758        final List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints();
759        if (WifiTracker.sVerboseLogging) {
760            Log.i(TAG, "updateAccessPoints called for: " + accessPoints);
761        }
762
763        boolean hasAvailableAccessPoints = false;
764        mAccessPointsPreferenceCategory.removePreference(mStatusMessagePreference);
765        cacheRemoveAllPrefs(mAccessPointsPreferenceCategory);
766
767        int index =
768                configureConnectedAccessPointPreferenceCategory(accessPoints) ? 1 : 0;
769        int numAccessPoints = accessPoints.size();
770        for (; index < numAccessPoints; index++) {
771            AccessPoint accessPoint = accessPoints.get(index);
772            // Ignore access points that are out of range.
773            if (accessPoint.isReachable()) {
774                String key = AccessPointPreference.generatePreferenceKey(accessPoint);
775                hasAvailableAccessPoints = true;
776                LongPressAccessPointPreference pref =
777                        (LongPressAccessPointPreference) getCachedPreference(key);
778                if (pref != null) {
779                    pref.setOrder(index);
780                    continue;
781                }
782                LongPressAccessPointPreference preference =
783                        createLongPressAccessPointPreference(accessPoint);
784                preference.setKey(key);
785                preference.setOrder(index);
786                if (mOpenSsid != null && mOpenSsid.equals(accessPoint.getSsidStr())
787                        && accessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
788                    if (!accessPoint.isSaved() || isDisabledByWrongPassword(accessPoint)) {
789                        onPreferenceTreeClick(preference);
790                        mOpenSsid = null;
791                    }
792                }
793                mAccessPointsPreferenceCategory.addPreference(preference);
794                accessPoint.setListener(WifiSettings.this);
795                preference.refresh();
796            }
797        }
798        removeCachedPrefs(mAccessPointsPreferenceCategory);
799        mAddPreference.setOrder(index);
800        mAccessPointsPreferenceCategory.addPreference(mAddPreference);
801        setAdditionalSettingsSummaries();
802
803        if (!hasAvailableAccessPoints) {
804            setProgressBarVisible(true);
805            Preference pref = new Preference(getPrefContext());
806            pref.setSelectable(false);
807            pref.setSummary(R.string.wifi_empty_list_wifi_on);
808            pref.setOrder(index++);
809            pref.setKey(PREF_KEY_EMPTY_WIFI_LIST);
810            mAccessPointsPreferenceCategory.addPreference(pref);
811        } else {
812            // Continuing showing progress bar for an additional delay to overlap with animation
813            getView().postDelayed(mHideProgressBarRunnable, 1700 /* delay millis */);
814        }
815    }
816
817    @NonNull
818    private LongPressAccessPointPreference createLongPressAccessPointPreference(
819            AccessPoint accessPoint) {
820        return new LongPressAccessPointPreference(accessPoint, getPrefContext(), mUserBadgeCache,
821                false /* forSavedNetworks */, R.drawable.ic_wifi_signal_0, this);
822    }
823
824    @NonNull
825    private ConnectedAccessPointPreference createConnectedAccessPointPreference(
826            AccessPoint accessPoint) {
827        return new ConnectedAccessPointPreference(accessPoint, getPrefContext(), mUserBadgeCache,
828                R.drawable.ic_wifi_signal_0, false /* forSavedNetworks */);
829    }
830
831    /**
832     * Configure the ConnectedAccessPointPreferenceCategory and return true if the Category was
833     * shown.
834     */
835    private boolean configureConnectedAccessPointPreferenceCategory(
836            List<AccessPoint> accessPoints) {
837        if (accessPoints.size() == 0) {
838            removeConnectedAccessPointPreference();
839            return false;
840        }
841
842        AccessPoint connectedAp = accessPoints.get(0);
843        if (!connectedAp.isActive()) {
844            removeConnectedAccessPointPreference();
845            return false;
846        }
847
848        // Is the preference category empty?
849        if (mConnectedAccessPointPreferenceCategory.getPreferenceCount() == 0) {
850            addConnectedAccessPointPreference(connectedAp);
851            return true;
852        }
853
854        // Is the previous currently connected SSID different from the new one?
855        AccessPointPreference preference = (AccessPointPreference)
856            (mConnectedAccessPointPreferenceCategory.getPreference(0));
857        // The AccessPoints need to be the same reference to ensure that updates are reflected
858        // in the UI.
859        if (preference.getAccessPoint() != connectedAp) {
860            removeConnectedAccessPointPreference();
861            addConnectedAccessPointPreference(connectedAp);
862            return true;
863        }
864
865        // Else same AP is connected, simply refresh the connected access point preference
866        // (first and only access point in this category).
867        ((AccessPointPreference) mConnectedAccessPointPreferenceCategory.getPreference(0))
868                .refresh();
869        return true;
870    }
871
872    /**
873     * Creates a Preference for the given {@link AccessPoint} and adds it to the
874     * {@link #mConnectedAccessPointPreferenceCategory}.
875     */
876    private void addConnectedAccessPointPreference(AccessPoint connectedAp) {
877        final ConnectedAccessPointPreference pref = createConnectedAccessPointPreference(
878                connectedAp);
879
880        // Launch details page on click.
881        pref.setOnGearClickListener(l -> {
882            pref.getAccessPoint().saveWifiState(pref.getExtras());
883
884            SettingsActivity activity = (SettingsActivity) WifiSettings.this.getActivity();
885            activity.startPreferencePanel(this,
886                    WifiNetworkDetailsFragment.class.getName(), pref.getExtras(),
887                    -1 /* resId */, pref.getTitle(), null, 0 /* resultRequestCode */);
888        });
889
890        pref.refresh();
891
892        mConnectedAccessPointPreferenceCategory.addPreference(pref);
893        mConnectedAccessPointPreferenceCategory.setVisible(true);
894        if (mClickedConnect) {
895            mClickedConnect = false;
896            scrollToPreference(mConnectedAccessPointPreferenceCategory);
897        }
898    }
899
900    /** Removes all preferences and hide the {@link #mConnectedAccessPointPreferenceCategory}. */
901    private void removeConnectedAccessPointPreference() {
902        mConnectedAccessPointPreferenceCategory.removeAll();
903        mConnectedAccessPointPreferenceCategory.setVisible(false);
904    }
905
906    private void setAdditionalSettingsSummaries() {
907        mAdditionalSettingsPreferenceCategory.addPreference(mConfigureWifiSettingsPreference);
908        final int defaultWakeupAvailable = getResources().getInteger(
909                com.android.internal.R.integer.config_wifi_wakeup_available);
910        boolean wifiWakeupAvailable = Settings.Global.getInt(
911                getContentResolver(), Settings.Global.WIFI_WAKEUP_AVAILABLE, defaultWakeupAvailable)
912                == 1;
913        if (wifiWakeupAvailable) {
914            mConfigureWifiSettingsPreference.setSummary(getString(
915                    isWifiWakeupEnabled()
916                    ? R.string.wifi_configure_settings_preference_summary_wakeup_on
917                    : R.string.wifi_configure_settings_preference_summary_wakeup_off));
918        }
919        int numSavedNetworks = mWifiTracker.getNumSavedNetworks();
920        if (numSavedNetworks > 0) {
921            mAdditionalSettingsPreferenceCategory.addPreference(mSavedNetworksPreference);
922            mSavedNetworksPreference.setSummary(
923                    getResources().getQuantityString(R.plurals.wifi_saved_access_points_summary,
924                            numSavedNetworks, numSavedNetworks));
925        } else {
926            mAdditionalSettingsPreferenceCategory.removePreference(mSavedNetworksPreference);
927        }
928    }
929
930    private boolean isWifiWakeupEnabled() {
931        PowerManager powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
932        ContentResolver contentResolver = getContentResolver();
933        return Settings.Global.getInt(contentResolver,
934                        Settings.Global.WIFI_WAKEUP_ENABLED, 0) == 1
935                && Settings.Global.getInt(contentResolver,
936                        Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1
937                && Settings.Global.getInt(contentResolver,
938                        Settings.Global.AIRPLANE_MODE_ON, 0) == 0
939                && Settings.Global.getInt(contentResolver,
940                        Settings.Global.NETWORK_RECOMMENDATIONS_ENABLED, 0) == 1
941                && !powerManager.isPowerSaveMode();
942    }
943
944    private void setOffMessage() {
945        final CharSequence title = getText(R.string.wifi_empty_list_wifi_off);
946        // Don't use WifiManager.isScanAlwaysAvailable() to check the Wi-Fi scanning mode. Instead,
947        // read the system settings directly. Because when the device is in Airplane mode, even if
948        // Wi-Fi scanning mode is on, WifiManager.isScanAlwaysAvailable() still returns "off".
949        final boolean wifiScanningMode = Settings.Global.getInt(getActivity().getContentResolver(),
950                Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1;
951        final CharSequence description = wifiScanningMode ? getText(R.string.wifi_scan_notify_text)
952                : getText(R.string.wifi_scan_notify_text_scanning_off);
953        final LinkifyUtils.OnClickListener clickListener = new LinkifyUtils.OnClickListener() {
954            @Override
955            public void onClick() {
956                final SettingsActivity activity = (SettingsActivity) getActivity();
957                activity.startPreferencePanel(WifiSettings.this,
958                        ScanningSettings.class.getName(),
959                        null, R.string.location_scanning_screen_title, null, null, 0);
960            }
961        };
962        mStatusMessagePreference.setText(title, description, clickListener);
963        removeConnectedAccessPointPreference();
964        mAccessPointsPreferenceCategory.removeAll();
965        mAccessPointsPreferenceCategory.addPreference(mStatusMessagePreference);
966    }
967
968    private void addMessagePreference(int messageId) {
969        mStatusMessagePreference.setTitle(messageId);
970        removeConnectedAccessPointPreference();
971        mAccessPointsPreferenceCategory.removeAll();
972        mAccessPointsPreferenceCategory.addPreference(mStatusMessagePreference);
973    }
974
975    protected void setProgressBarVisible(boolean visible) {
976        if (mProgressHeader != null) {
977            mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE);
978        }
979    }
980
981    /**
982     * Renames/replaces "Next" button when appropriate. "Next" button usually exists in
983     * Wifi setup screens, not in usual wifi settings screen.
984     *
985     * @param enabled true when the device is connected to a wifi network.
986     */
987    private void changeNextButtonState(boolean enabled) {
988        if (mEnableNextOnConnection && hasNextButton()) {
989            getNextButton().setEnabled(enabled);
990        }
991    }
992
993    @Override
994    public void onForget(WifiDialog dialog) {
995        forget();
996    }
997
998    @Override
999    public void onSubmit(WifiDialog dialog) {
1000        if (mDialog != null) {
1001            submit(mDialog.getController());
1002        }
1003    }
1004
1005    /* package */ void submit(WifiConfigController configController) {
1006
1007        final WifiConfiguration config = configController.getConfig();
1008
1009        if (config == null) {
1010            if (mSelectedAccessPoint != null
1011                    && mSelectedAccessPoint.isSaved()) {
1012                connect(mSelectedAccessPoint.getConfig(), true /* isSavedNetwork */);
1013            }
1014        } else if (configController.getMode() == WifiConfigUiBase.MODE_MODIFY) {
1015            mWifiManager.save(config, mSaveListener);
1016        } else {
1017            mWifiManager.save(config, mSaveListener);
1018            if (mSelectedAccessPoint != null) { // Not an "Add network"
1019                connect(config, false /* isSavedNetwork */);
1020            }
1021        }
1022
1023        mWifiTracker.resumeScanning();
1024    }
1025
1026    /* package */ void forget() {
1027        mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_FORGET);
1028        if (!mSelectedAccessPoint.isSaved()) {
1029            if (mSelectedAccessPoint.getNetworkInfo() != null &&
1030                    mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED) {
1031                // Network is active but has no network ID - must be ephemeral.
1032                mWifiManager.disableEphemeralNetwork(
1033                        AccessPoint.convertToQuotedString(mSelectedAccessPoint.getSsidStr()));
1034            } else {
1035                // Should not happen, but a monkey seems to trigger it
1036                Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig());
1037                return;
1038            }
1039        } else if (mSelectedAccessPoint.getConfig().isPasspoint()) {
1040            mWifiManager.removePasspointConfiguration(mSelectedAccessPoint.getConfig().FQDN);
1041        } else {
1042            mWifiManager.forget(mSelectedAccessPoint.getConfig().networkId, mForgetListener);
1043        }
1044
1045        mWifiTracker.resumeScanning();
1046
1047        // We need to rename/replace "Next" button in wifi setup context.
1048        changeNextButtonState(false);
1049    }
1050
1051    protected void connect(final WifiConfiguration config, boolean isSavedNetwork) {
1052        // Log subtype if configuration is a saved network.
1053        mMetricsFeatureProvider.action(getVisibilityLogger(), MetricsEvent.ACTION_WIFI_CONNECT,
1054                isSavedNetwork);
1055        mWifiManager.connect(config, mConnectListener);
1056        mClickedConnect = true;
1057    }
1058
1059    protected void connect(final int networkId, boolean isSavedNetwork) {
1060        // Log subtype if configuration is a saved network.
1061        mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT,
1062                isSavedNetwork);
1063        mWifiManager.connect(networkId, mConnectListener);
1064    }
1065
1066    /**
1067     * Called when "add network" button is pressed.
1068     */
1069    /* package */ void onAddNetworkPressed() {
1070        mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_ADD_NETWORK);
1071        // No exact access point is selected.
1072        mSelectedAccessPoint = null;
1073        showDialog(null, WifiConfigUiBase.MODE_CONNECT);
1074    }
1075
1076    @Override
1077    public int getHelpResource() {
1078        return R.string.help_url_wifi;
1079    }
1080
1081    @Override
1082    public void onAccessPointChanged(final AccessPoint accessPoint) {
1083        Log.d(TAG, "onAccessPointChanged (singular) callback initiated");
1084        View view = getView();
1085        if (view != null) {
1086            view.post(new Runnable() {
1087                @Override
1088                public void run() {
1089                    Object tag = accessPoint.getTag();
1090                    if (tag != null) {
1091                        ((AccessPointPreference) tag).refresh();
1092                    }
1093                }
1094            });
1095        }
1096    }
1097
1098    @Override
1099    public void onLevelChanged(AccessPoint accessPoint) {
1100        ((AccessPointPreference) accessPoint.getTag()).onLevelChanged();
1101    }
1102
1103    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
1104        new BaseSearchIndexProvider() {
1105            @Override
1106            public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
1107                final List<SearchIndexableRaw> result = new ArrayList<>();
1108                final Resources res = context.getResources();
1109
1110                // Add fragment title if we are showing this fragment
1111                if (res.getBoolean(R.bool.config_show_wifi_settings)) {
1112                    SearchIndexableRaw data = new SearchIndexableRaw(context);
1113                    data.title = res.getString(R.string.wifi_settings);
1114                    data.screenTitle = res.getString(R.string.wifi_settings);
1115                    data.keywords = res.getString(R.string.keywords_wifi);
1116                    data.key = DATA_KEY_REFERENCE;
1117                    result.add(data);
1118                }
1119
1120                return result;
1121            }
1122        };
1123
1124    private static class SummaryProvider
1125            implements SummaryLoader.SummaryProvider, OnSummaryChangeListener {
1126
1127        private final Context mContext;
1128        private final SummaryLoader mSummaryLoader;
1129
1130        @VisibleForTesting
1131        WifiSummaryUpdater mSummaryHelper;
1132
1133        public SummaryProvider(Context context, SummaryLoader summaryLoader) {
1134            mContext = context;
1135            mSummaryLoader = summaryLoader;
1136            mSummaryHelper = new WifiSummaryUpdater(mContext, this);
1137        }
1138
1139
1140        @Override
1141        public void setListening(boolean listening) {
1142            mSummaryHelper.register(listening);
1143        }
1144
1145        @Override
1146        public void onSummaryChanged(String summary) {
1147            mSummaryLoader.setSummary(this, summary);
1148        }
1149    }
1150
1151    public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
1152            = new SummaryLoader.SummaryProviderFactory() {
1153        @Override
1154        public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
1155                                                                   SummaryLoader summaryLoader) {
1156            return new SummaryProvider(activity, summaryLoader);
1157        }
1158    };
1159}
1160