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