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