WifiSettings.java revision ba1b520505508fd96cbf6cd287112d09365f8514
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 android.app.Activity;
20import android.app.Dialog;
21import android.app.admin.DevicePolicyManager;
22import android.content.BroadcastReceiver;
23import android.content.ComponentName;
24import android.content.ContentResolver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.pm.PackageManager;
29import android.content.pm.PackageManager.NameNotFoundException;
30import android.content.res.Resources;
31import android.net.ConnectivityManager;
32import android.net.NetworkInfo;
33import android.net.NetworkInfo.State;
34import android.net.wifi.WifiConfiguration;
35import android.net.wifi.WifiManager;
36import android.net.wifi.WpsInfo;
37import android.nfc.NfcAdapter;
38import android.os.Bundle;
39import android.os.HandlerThread;
40import android.os.Process;
41import android.provider.Settings;
42import android.support.v7.preference.Preference;
43import android.support.v7.preference.PreferenceViewHolder;
44import android.text.Spannable;
45import android.text.TextUtils;
46import android.text.style.TextAppearanceSpan;
47import android.util.Log;
48import android.view.ContextMenu;
49import android.view.ContextMenu.ContextMenuInfo;
50import android.view.Menu;
51import android.view.MenuInflater;
52import android.view.MenuItem;
53import android.view.View;
54import android.widget.ProgressBar;
55import android.widget.TextView;
56import android.widget.TextView.BufferType;
57import android.widget.Toast;
58
59import com.android.internal.logging.MetricsLogger;
60import com.android.internal.logging.MetricsProto.MetricsEvent;
61import com.android.settings.LinkifyUtils;
62import com.android.settings.R;
63import com.android.settings.RestrictedSettingsFragment;
64import com.android.settings.SettingsActivity;
65import com.android.settings.dashboard.SummaryLoader;
66import com.android.settings.location.ScanningSettings;
67import com.android.settings.search.BaseSearchIndexProvider;
68import com.android.settings.search.Indexable;
69import com.android.settings.search.SearchIndexableRaw;
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.WifiStatusTracker;
75import com.android.settingslib.wifi.WifiTracker;
76
77import java.util.ArrayList;
78import java.util.Collection;
79import java.util.List;
80
81import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
82
83/**
84 * Two types of UI are provided here.
85 *
86 * The first is for "usual Settings", appearing as any other Setup fragment.
87 *
88 * The second is for Setup Wizard, with a simplified interface that hides the action bar
89 * and menus.
90 */
91public class WifiSettings extends RestrictedSettingsFragment
92        implements Indexable, WifiTracker.WifiListener, AccessPointListener,
93        WifiDialog.WifiDialogListener {
94
95    private static final String TAG = "WifiSettings";
96
97    /* package */ static final int MENU_ID_WPS_PBC = Menu.FIRST;
98    private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1;
99    private static final int MENU_ID_ADVANCED = Menu.FIRST + 4;
100    private static final int MENU_ID_SCAN = Menu.FIRST + 5;
101    private static final int MENU_ID_CONNECT = Menu.FIRST + 6;
102    private static final int MENU_ID_FORGET = Menu.FIRST + 7;
103    private static final int MENU_ID_MODIFY = Menu.FIRST + 8;
104    private static final int MENU_ID_WRITE_NFC = Menu.FIRST + 9;
105    private static final int MENU_ID_CONFIGURE = Menu.FIRST + 10;
106
107    public static final int WIFI_DIALOG_ID = 1;
108    /* package */ static final int WPS_PBC_DIALOG_ID = 2;
109    private static final int WPS_PIN_DIALOG_ID = 3;
110    private static final int WRITE_NFC_DIALOG_ID = 6;
111
112    // Instance state keys
113    private static final String SAVE_DIALOG_MODE = "dialog_mode";
114    private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
115    private static final String SAVED_WIFI_NFC_DIALOG_STATE = "wifi_nfc_dlg_state";
116
117    private static final String PREF_KEY_EMPTY_WIFI_LIST = "wifi_empty_list";
118
119    protected WifiManager mWifiManager;
120    private WifiManager.ActionListener mConnectListener;
121    private WifiManager.ActionListener mSaveListener;
122    private WifiManager.ActionListener mForgetListener;
123
124    private WifiEnabler mWifiEnabler;
125    // An access point being editted is stored here.
126    private AccessPoint mSelectedAccessPoint;
127
128    private WifiDialog mDialog;
129    private WriteWifiConfigToNfcDialog mWifiToNfcDialog;
130
131    private ProgressBar mProgressHeader;
132
133    // this boolean extra specifies whether to disable the Next button when not connected. Used by
134    // account creation outside of setup wizard.
135    private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
136    // This string extra specifies a network to open the connect dialog on, so the user can enter
137    // network credentials.  This is used by quick settings for secured networks.
138    private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
139
140    // should Next button only be enabled when we have a connection?
141    private boolean mEnableNextOnConnection;
142
143    // Save the dialog details
144    private int mDialogMode;
145    private AccessPoint mDlgAccessPoint;
146    private Bundle mAccessPointSavedState;
147    private Bundle mWifiNfcDialogSavedState;
148
149    private WifiTracker mWifiTracker;
150    private String mOpenSsid;
151
152    private HandlerThread mBgThread;
153
154    private AccessPointPreference.UserBadgeCache mUserBadgeCache;
155    private Preference mAddPreference;
156
157    private MenuItem mScanMenuItem;
158
159    /* End of "used in Wifi Setup context" */
160
161    public WifiSettings() {
162        super(DISALLOW_CONFIG_WIFI);
163    }
164
165    @Override
166    public void onViewCreated(View view, Bundle savedInstanceState) {
167        super.onViewCreated(view, savedInstanceState);
168        final Activity activity = getActivity();
169        if (activity != null) {
170            mProgressHeader = (ProgressBar) setPinnedHeaderView(R.layout.wifi_progress_header);
171            setProgressBarVisible(false);
172        }
173    }
174
175    @Override
176    public void onCreate(Bundle icicle) {
177        super.onCreate(icicle);
178        addPreferencesFromResource(R.xml.wifi_settings);
179        mAddPreference = new Preference(getContext());
180        mAddPreference.setIcon(R.drawable.ic_menu_add_inset);
181        mAddPreference.setTitle(R.string.wifi_add_network);
182
183        mUserBadgeCache = new AccessPointPreference.UserBadgeCache(getPackageManager());
184
185        mBgThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
186        mBgThread.start();
187    }
188
189    @Override
190    public void onDestroy() {
191        mBgThread.quit();
192        super.onDestroy();
193    }
194
195    @Override
196    public void onActivityCreated(Bundle savedInstanceState) {
197        super.onActivityCreated(savedInstanceState);
198
199        mWifiTracker =
200                new WifiTracker(getActivity(), this, mBgThread.getLooper(), true, true, false);
201        mWifiManager = mWifiTracker.getManager();
202
203        mConnectListener = new WifiManager.ActionListener() {
204                                   @Override
205                                   public void onSuccess() {
206                                   }
207                                   @Override
208                                   public void onFailure(int reason) {
209                                       Activity activity = getActivity();
210                                       if (activity != null) {
211                                           Toast.makeText(activity,
212                                                R.string.wifi_failed_connect_message,
213                                                Toast.LENGTH_SHORT).show();
214                                       }
215                                   }
216                               };
217
218        mSaveListener = new WifiManager.ActionListener() {
219                                @Override
220                                public void onSuccess() {
221                                }
222                                @Override
223                                public void onFailure(int reason) {
224                                    Activity activity = getActivity();
225                                    if (activity != null) {
226                                        Toast.makeText(activity,
227                                            R.string.wifi_failed_save_message,
228                                            Toast.LENGTH_SHORT).show();
229                                    }
230                                }
231                            };
232
233        mForgetListener = new WifiManager.ActionListener() {
234                                   @Override
235                                   public void onSuccess() {
236                                   }
237                                   @Override
238                                   public void onFailure(int reason) {
239                                       Activity activity = getActivity();
240                                       if (activity != null) {
241                                           Toast.makeText(activity,
242                                               R.string.wifi_failed_forget_message,
243                                               Toast.LENGTH_SHORT).show();
244                                       }
245                                   }
246                               };
247
248        if (savedInstanceState != null) {
249            mDialogMode = savedInstanceState.getInt(SAVE_DIALOG_MODE);
250            if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
251                mAccessPointSavedState =
252                    savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
253            }
254
255            if (savedInstanceState.containsKey(SAVED_WIFI_NFC_DIALOG_STATE)) {
256                mWifiNfcDialogSavedState =
257                    savedInstanceState.getBundle(SAVED_WIFI_NFC_DIALOG_STATE);
258            }
259        }
260
261        // if we're supposed to enable/disable the Next button based on our current connection
262        // state, start it off in the right state
263        Intent intent = getActivity().getIntent();
264        mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
265
266        if (mEnableNextOnConnection) {
267            if (hasNextButton()) {
268                final ConnectivityManager connectivity = (ConnectivityManager)
269                        getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
270                if (connectivity != null) {
271                    NetworkInfo info = connectivity.getNetworkInfo(
272                            ConnectivityManager.TYPE_WIFI);
273                    changeNextButtonState(info.isConnected());
274                }
275            }
276        }
277
278        registerForContextMenu(getListView());
279        setHasOptionsMenu(true);
280
281        if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) {
282            mOpenSsid = intent.getStringExtra(EXTRA_START_CONNECT_SSID);
283            onAccessPointsChanged();
284        }
285    }
286
287    @Override
288    public void onDestroyView() {
289        super.onDestroyView();
290
291        if (mWifiEnabler != null) {
292            mWifiEnabler.teardownSwitchBar();
293        }
294    }
295
296    @Override
297    public void onStart() {
298        super.onStart();
299
300        // On/off switch is hidden for Setup Wizard (returns null)
301        mWifiEnabler = createWifiEnabler();
302
303        mWifiTracker.startTracking();
304    }
305
306    /**
307     * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard)
308     */
309    /* package */ WifiEnabler createWifiEnabler() {
310        final SettingsActivity activity = (SettingsActivity) getActivity();
311        return new WifiEnabler(activity, activity.getSwitchBar());
312    }
313
314    @Override
315    public void onResume() {
316        final Activity activity = getActivity();
317        super.onResume();
318        removePreference("dummy");
319        if (mWifiEnabler != null) {
320            mWifiEnabler.resume(activity);
321        }
322
323        activity.invalidateOptionsMenu();
324    }
325
326    @Override
327    public void onPause() {
328        super.onPause();
329        if (mWifiEnabler != null) {
330            mWifiEnabler.pause();
331        }
332    }
333
334    @Override
335    public void onStop() {
336        super.onStop();
337        mWifiTracker.stopTracking();
338    }
339
340    @Override
341    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
342        // If the user is not allowed to configure wifi, do not show the menu.
343        if (isUiRestricted()) return;
344
345        addOptionsMenuItems(menu);
346        super.onCreateOptionsMenu(menu, inflater);
347    }
348
349    /**
350     * @param menu
351     */
352    void addOptionsMenuItems(Menu menu) {
353        final boolean wifiIsEnabled = mWifiTracker.isWifiEnabled();
354        mScanMenuItem = menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.menu_stats_refresh);
355        mScanMenuItem.setEnabled(wifiIsEnabled)
356               .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
357        menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
358                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
359        menu.add(Menu.NONE, MENU_ID_CONFIGURE, 0, R.string.wifi_menu_configure)
360                .setIcon(R.drawable.ic_settings_24dp)
361                .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
362    }
363
364    @Override
365    protected int getMetricsCategory() {
366        return MetricsEvent.WIFI;
367    }
368
369    @Override
370    public void onSaveInstanceState(Bundle outState) {
371        super.onSaveInstanceState(outState);
372
373        // If the dialog is showing, save its state.
374        if (mDialog != null && mDialog.isShowing()) {
375            outState.putInt(SAVE_DIALOG_MODE, mDialogMode);
376            if (mDlgAccessPoint != null) {
377                mAccessPointSavedState = new Bundle();
378                mDlgAccessPoint.saveWifiState(mAccessPointSavedState);
379                outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
380            }
381        }
382
383        if (mWifiToNfcDialog != null && mWifiToNfcDialog.isShowing()) {
384            Bundle savedState = new Bundle();
385            mWifiToNfcDialog.saveState(savedState);
386            outState.putBundle(SAVED_WIFI_NFC_DIALOG_STATE, savedState);
387        }
388    }
389
390    @Override
391    public boolean onOptionsItemSelected(MenuItem item) {
392        // If the user is not allowed to configure wifi, do not handle menu selections.
393        if (isUiRestricted()) return false;
394
395        switch (item.getItemId()) {
396            case MENU_ID_WPS_PBC:
397                showDialog(WPS_PBC_DIALOG_ID);
398                return true;
399            case MENU_ID_WPS_PIN:
400                showDialog(WPS_PIN_DIALOG_ID);
401                return true;
402            case MENU_ID_SCAN:
403                MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_FORCE_SCAN);
404                mWifiTracker.forceScan();
405                return true;
406            case MENU_ID_ADVANCED:
407                if (getActivity() instanceof SettingsActivity) {
408                    ((SettingsActivity) getActivity()).startPreferencePanel(
409                            AdvancedWifiSettings.class.getCanonicalName(), null,
410                            R.string.wifi_advanced_titlebar, null, this, 0);
411                } else {
412                    startFragment(this, AdvancedWifiSettings.class.getCanonicalName(),
413                            R.string.wifi_advanced_titlebar, -1 /* Do not request a results */,
414                            null);
415                }
416                return true;
417            case MENU_ID_CONFIGURE:
418                if (getActivity() instanceof SettingsActivity) {
419                    ((SettingsActivity) getActivity()).startPreferencePanel(
420                            ConfigureWifiSettings.class.getCanonicalName(), null,
421                            R.string.wifi_configure_titlebar, null, this, 0);
422                } else {
423                    startFragment(this, ConfigureWifiSettings.class.getCanonicalName(),
424                            R.string.wifi_configure_titlebar, -1 /* Do not request a results */,
425                            null);
426                }
427                return true;
428
429        }
430        return super.onOptionsItemSelected(item);
431    }
432
433    @Override
434    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
435            Preference preference = (Preference) view.getTag();
436
437            if (preference instanceof LongPressAccessPointPreference) {
438                mSelectedAccessPoint =
439                        ((LongPressAccessPointPreference) preference).getAccessPoint();
440                menu.setHeaderTitle(mSelectedAccessPoint.getSsid());
441                if (mSelectedAccessPoint.isConnectable()) {
442                    menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
443                }
444
445                WifiConfiguration config = mSelectedAccessPoint.getConfig();
446                // Some configs are ineditable
447                if (isEditabilityLockedDown(getActivity(), config)) {
448                    return;
449                }
450
451                if (mSelectedAccessPoint.isSaved() || mSelectedAccessPoint.isEphemeral()) {
452                    // Allow forgetting a network if either the network is saved or ephemerally
453                    // connected. (In the latter case, "forget" blacklists the network so it won't
454                    // be used again, ephemerally).
455                    menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
456                }
457                if (mSelectedAccessPoint.isSaved()) {
458                    menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
459                    NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
460                    if (nfcAdapter != null && nfcAdapter.isEnabled() &&
461                            mSelectedAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
462                        // Only allow writing of NFC tags for password-protected networks.
463                        menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc);
464                    }
465                }
466            }
467    }
468
469    @Override
470    public boolean onContextItemSelected(MenuItem item) {
471        if (mSelectedAccessPoint == null) {
472            return super.onContextItemSelected(item);
473        }
474        switch (item.getItemId()) {
475            case MENU_ID_CONNECT: {
476                boolean isSavedNetwork = mSelectedAccessPoint.isSaved();
477                if (isSavedNetwork) {
478                    connect(mSelectedAccessPoint.getConfig(), isSavedNetwork);
479                } else if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE) {
480                    /** Bypass dialog for unsecured networks */
481                    mSelectedAccessPoint.generateOpenNetworkConfig();
482                    connect(mSelectedAccessPoint.getConfig(), isSavedNetwork);
483                } else {
484                    showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_CONNECT);
485                }
486                return true;
487            }
488            case MENU_ID_FORGET: {
489                forget();
490                return true;
491            }
492            case MENU_ID_MODIFY: {
493                showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_MODIFY);
494                return true;
495            }
496            case MENU_ID_WRITE_NFC:
497                showDialog(WRITE_NFC_DIALOG_ID);
498                return true;
499
500        }
501        return super.onContextItemSelected(item);
502    }
503
504    @Override
505    public boolean onPreferenceTreeClick(Preference preference) {
506        if (preference instanceof LongPressAccessPointPreference) {
507            mSelectedAccessPoint = ((LongPressAccessPointPreference) preference).getAccessPoint();
508            if (mSelectedAccessPoint == null) {
509                return false;
510            }
511            /** Bypass dialog for unsecured, unsaved, and inactive networks */
512            if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE &&
513                    !mSelectedAccessPoint.isSaved() && !mSelectedAccessPoint.isActive()) {
514                mSelectedAccessPoint.generateOpenNetworkConfig();
515                connect(mSelectedAccessPoint.getConfig(), false /* isSavedNetwork */);
516            } else if (mSelectedAccessPoint.isSaved()) {
517                showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_VIEW);
518            } else {
519                showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_CONNECT);
520            }
521        } else if (preference == mAddPreference) {
522            onAddNetworkPressed();
523        } else {
524            return super.onPreferenceTreeClick(preference);
525        }
526        return true;
527    }
528
529    private void showDialog(AccessPoint accessPoint, int dialogMode) {
530        if (accessPoint != null) {
531            WifiConfiguration config = accessPoint.getConfig();
532            if (isEditabilityLockedDown(getActivity(), config) && accessPoint.isActive()) {
533                RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(),
534                        RestrictedLockUtils.getDeviceOwner(getActivity()));
535                return;
536            }
537        }
538
539        if (mDialog != null) {
540            removeDialog(WIFI_DIALOG_ID);
541            mDialog = null;
542        }
543
544        // Save the access point and edit mode
545        mDlgAccessPoint = accessPoint;
546        mDialogMode = dialogMode;
547
548        showDialog(WIFI_DIALOG_ID);
549    }
550
551    @Override
552    public Dialog onCreateDialog(int dialogId) {
553        switch (dialogId) {
554            case WIFI_DIALOG_ID:
555                AccessPoint ap = mDlgAccessPoint; // For manual launch
556                if (ap == null) { // For re-launch from saved state
557                    if (mAccessPointSavedState != null) {
558                        ap = new AccessPoint(getActivity(), mAccessPointSavedState);
559                        // For repeated orientation changes
560                        mDlgAccessPoint = ap;
561                        // Reset the saved access point data
562                        mAccessPointSavedState = null;
563                    }
564                }
565                // If it's null, fine, it's for Add Network
566                mSelectedAccessPoint = ap;
567                mDialog = new WifiDialog(getActivity(), this, ap, mDialogMode,
568                        /* no hide submit/connect */ false);
569                return mDialog;
570            case WPS_PBC_DIALOG_ID:
571                return new WpsDialog(getActivity(), WpsInfo.PBC);
572            case WPS_PIN_DIALOG_ID:
573                return new WpsDialog(getActivity(), WpsInfo.DISPLAY);
574            case WRITE_NFC_DIALOG_ID:
575                if (mSelectedAccessPoint != null) {
576                    mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
577                            getActivity(), mSelectedAccessPoint.getConfig().networkId,
578                            mSelectedAccessPoint.getSecurity(),
579                            mWifiManager);
580                } else if (mWifiNfcDialogSavedState != null) {
581                    mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
582                            getActivity(), mWifiNfcDialogSavedState, mWifiManager);
583                }
584
585                return mWifiToNfcDialog;
586        }
587        return super.onCreateDialog(dialogId);
588    }
589
590    /**
591     * Shows the latest access points available with supplemental information like
592     * the strength of network and the security for it.
593     */
594    @Override
595    public void onAccessPointsChanged() {
596        // Safeguard from some delayed event handling
597        if (getActivity() == null) return;
598        final int wifiState = mWifiManager.getWifiState();
599        if (isUiRestricted()) {
600            if (!isUiRestrictedByOnlyAdmin()) {
601                if (WifiManager.WIFI_STATE_DISABLED == wifiState) {
602                    addMessagePreference(R.string.wifi_empty_list_wifi_off);
603                }
604                else {
605                    addMessagePreference(R.string.wifi_empty_list_user_restricted);
606                }
607            }
608            getPreferenceScreen().removeAll();
609            return;
610        }
611
612        switch (wifiState) {
613            case WifiManager.WIFI_STATE_ENABLED:
614                // AccessPoints are automatically sorted with TreeSet.
615                final Collection<AccessPoint> accessPoints =
616                        mWifiTracker.getAccessPoints();
617
618                boolean hasAvailableAccessPoints = false;
619                int index = 0;
620                cacheRemoveAllPrefs(getPreferenceScreen());
621                for (AccessPoint accessPoint : accessPoints) {
622                    // Ignore access points that are out of range.
623                    if (accessPoint.getLevel() != -1) {
624                        String key = accessPoint.getBssid();
625                        if (TextUtils.isEmpty(key)) {
626                            key = accessPoint.getSsidStr();
627                        }
628                        hasAvailableAccessPoints = true;
629                        LongPressAccessPointPreference pref = (LongPressAccessPointPreference)
630                                getCachedPreference(key);
631                        if (pref != null) {
632                            pref.setOrder(index++);
633                            continue;
634                        }
635                        LongPressAccessPointPreference
636                                preference = new LongPressAccessPointPreference(accessPoint,
637                                getPrefContext(), mUserBadgeCache, false,
638                                R.drawable.ic_wifi_signal_0, this);
639                        preference.setKey(key);
640                        preference.setOrder(index++);
641                        if (mOpenSsid != null && mOpenSsid.equals(accessPoint.getSsidStr())
642                                && !accessPoint.isSaved()
643                                && accessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
644                            onPreferenceTreeClick(preference);
645                            mOpenSsid = null;
646                        }
647                        getPreferenceScreen().addPreference(preference);
648                        accessPoint.setListener(this);
649                        preference.refresh();
650                    }
651                }
652                removeCachedPrefs(getPreferenceScreen());
653                if (!hasAvailableAccessPoints) {
654                    setProgressBarVisible(true);
655                    Preference pref = new Preference(getContext()) {
656                        @Override
657                        public void onBindViewHolder(PreferenceViewHolder holder) {
658                            super.onBindViewHolder(holder);
659                            // Show a line on each side of add network.
660                            holder.setDividerAllowedBelow(true);
661                        }
662                    };
663                    pref.setSelectable(false);
664                    pref.setSummary(R.string.wifi_empty_list_wifi_on);
665                    pref.setOrder(0);
666                    pref.setKey(PREF_KEY_EMPTY_WIFI_LIST);
667                    getPreferenceScreen().addPreference(pref);
668                    mAddPreference.setOrder(1);
669                    getPreferenceScreen().addPreference(mAddPreference);
670                } else {
671                    mAddPreference.setOrder(index++);
672                    getPreferenceScreen().addPreference(mAddPreference);
673                    setProgressBarVisible(false);
674                }
675                if (mScanMenuItem != null) {
676                    mScanMenuItem.setEnabled(true);
677                }
678                break;
679
680            case WifiManager.WIFI_STATE_ENABLING:
681                getPreferenceScreen().removeAll();
682                setProgressBarVisible(true);
683                break;
684
685            case WifiManager.WIFI_STATE_DISABLING:
686                addMessagePreference(R.string.wifi_stopping);
687                setProgressBarVisible(true);
688                break;
689
690            case WifiManager.WIFI_STATE_DISABLED:
691                setOffMessage();
692                setProgressBarVisible(false);
693                if (mScanMenuItem != null) {
694                    mScanMenuItem.setEnabled(false);
695                }
696                break;
697        }
698    }
699
700    private void setOffMessage() {
701        if (isUiRestricted()) {
702            if (!isUiRestrictedByOnlyAdmin()) {
703                addMessagePreference(R.string.wifi_empty_list_wifi_off);
704            }
705            getPreferenceScreen().removeAll();
706            return;
707        }
708
709        TextView emptyTextView = getEmptyTextView();
710        if (emptyTextView == null) {
711            return;
712        }
713
714        final CharSequence briefText = getText(R.string.wifi_empty_list_wifi_off);
715
716        // Don't use WifiManager.isScanAlwaysAvailable() to check the Wi-Fi scanning mode. Instead,
717        // read the system settings directly. Because when the device is in Airplane mode, even if
718        // Wi-Fi scanning mode is on, WifiManager.isScanAlwaysAvailable() still returns "off".
719        final ContentResolver resolver = getActivity().getContentResolver();
720        final boolean wifiScanningMode = Settings.Global.getInt(
721                resolver, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1;
722
723        if (!wifiScanningMode) {
724            // Show only the brief text if the user is not allowed to configure scanning settings,
725            // or the scanning mode has been turned off.
726            emptyTextView.setText(briefText, BufferType.SPANNABLE);
727        } else {
728            // Append the description of scanning settings with link.
729            final StringBuilder contentBuilder = new StringBuilder();
730            contentBuilder.append(briefText);
731            contentBuilder.append("\n\n");
732            contentBuilder.append(getText(R.string.wifi_scan_notify_text));
733            LinkifyUtils.linkify(emptyTextView, contentBuilder, new LinkifyUtils.OnClickListener() {
734                @Override
735                public void onClick() {
736                    final SettingsActivity activity =
737                            (SettingsActivity) WifiSettings.this.getActivity();
738                    activity.startPreferencePanel(ScanningSettings.class.getName(), null,
739                            R.string.location_scanning_screen_title, null, null, 0);
740                }
741            });
742        }
743        // Embolden and enlarge the brief description anyway.
744        Spannable boldSpan = (Spannable) emptyTextView.getText();
745        boldSpan.setSpan(
746                new TextAppearanceSpan(getActivity(), android.R.style.TextAppearance_Medium), 0,
747                briefText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
748        getPreferenceScreen().removeAll();
749    }
750
751    private void addMessagePreference(int messageId) {
752        TextView emptyTextView = getEmptyTextView();
753        if (emptyTextView != null) emptyTextView.setText(messageId);
754        getPreferenceScreen().removeAll();
755    }
756
757    protected void setProgressBarVisible(boolean visible) {
758        if (mProgressHeader != null) {
759            mProgressHeader.setVisibility(visible && !isUiRestricted() ? View.VISIBLE : View.GONE);
760        }
761    }
762
763    @Override
764    public void onWifiStateChanged(int state) {
765        switch (state) {
766            case WifiManager.WIFI_STATE_ENABLING:
767                addMessagePreference(R.string.wifi_starting);
768                setProgressBarVisible(true);
769                break;
770
771            case WifiManager.WIFI_STATE_DISABLED:
772                setOffMessage();
773                setProgressBarVisible(false);
774                break;
775        }
776    }
777
778    @Override
779    public void onConnectedChanged() {
780        changeNextButtonState(mWifiTracker.isConnected());
781    }
782
783    /**
784     * Renames/replaces "Next" button when appropriate. "Next" button usually exists in
785     * Wifi setup screens, not in usual wifi settings screen.
786     *
787     * @param enabled true when the device is connected to a wifi network.
788     */
789    private void changeNextButtonState(boolean enabled) {
790        if (mEnableNextOnConnection && hasNextButton()) {
791            getNextButton().setEnabled(enabled);
792        }
793    }
794
795    @Override
796    public void onForget(WifiDialog dialog) {
797        forget();
798    }
799
800    @Override
801    public void onSubmit(WifiDialog dialog) {
802        if (mDialog != null) {
803            submit(mDialog.getController());
804        }
805    }
806
807    /* package */ void submit(WifiConfigController configController) {
808
809        final WifiConfiguration config = configController.getConfig();
810
811        if (config == null) {
812            if (mSelectedAccessPoint != null
813                    && mSelectedAccessPoint.isSaved()) {
814                connect(mSelectedAccessPoint.getConfig(), true /* isSavedNetwork */);
815            }
816        } else if (configController.getMode() == WifiConfigUiBase.MODE_MODIFY) {
817            mWifiManager.save(config, mSaveListener);
818        } else {
819            mWifiManager.save(config, mSaveListener);
820            if (mSelectedAccessPoint != null) { // Not an "Add network"
821                connect(config, false /* isSavedNetwork */);
822            }
823        }
824
825        mWifiTracker.resumeScanning();
826    }
827
828    /* package */ void forget() {
829        MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_FORGET);
830        if (!mSelectedAccessPoint.isSaved()) {
831            if (mSelectedAccessPoint.getNetworkInfo() != null &&
832                    mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED) {
833                // Network is active but has no network ID - must be ephemeral.
834                mWifiManager.disableEphemeralNetwork(
835                        AccessPoint.convertToQuotedString(mSelectedAccessPoint.getSsidStr()));
836            } else {
837                // Should not happen, but a monkey seems to trigger it
838                Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig());
839                return;
840            }
841        } else if (mSelectedAccessPoint.getConfig().isPasspoint()) {
842            mWifiManager.removePasspointConfiguration(mSelectedAccessPoint.getConfig().FQDN);
843        } else {
844            mWifiManager.forget(mSelectedAccessPoint.getConfig().networkId, mForgetListener);
845        }
846
847        mWifiTracker.resumeScanning();
848
849        // We need to rename/replace "Next" button in wifi setup context.
850        changeNextButtonState(false);
851    }
852
853    protected void connect(final WifiConfiguration config, boolean isSavedNetwork) {
854        // Log subtype if configuration is a saved network.
855        MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT,
856                isSavedNetwork);
857        mWifiManager.connect(config, mConnectListener);
858    }
859
860    protected void connect(final int networkId, boolean isSavedNetwork) {
861        // Log subtype if configuration is a saved network.
862        MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT,
863                isSavedNetwork);
864        mWifiManager.connect(networkId, mConnectListener);
865    }
866
867    /**
868     * Called when "add network" button is pressed.
869     */
870    /* package */ void onAddNetworkPressed() {
871        MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_ADD_NETWORK);
872        // No exact access point is selected.
873        mSelectedAccessPoint = null;
874        showDialog(null, WifiConfigUiBase.MODE_CONNECT);
875    }
876
877    @Override
878    protected int getHelpResource() {
879        return R.string.help_url_wifi;
880    }
881
882    @Override
883    public void onAccessPointChanged(final AccessPoint accessPoint) {
884        View view = getView();
885        if (view != null) {
886            view.post(new Runnable() {
887                @Override
888                public void run() {
889                    Object tag = accessPoint.getTag();
890                    if (tag != null) {
891                        ((LongPressAccessPointPreference) tag).refresh();
892                    }
893                }
894            });
895        }
896    }
897
898    @Override
899    public void onLevelChanged(AccessPoint accessPoint) {
900        ((LongPressAccessPointPreference) accessPoint.getTag()).onLevelChanged();
901    }
902
903    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
904        new BaseSearchIndexProvider() {
905            @Override
906            public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
907                final List<SearchIndexableRaw> result = new ArrayList<>();
908                final Resources res = context.getResources();
909
910                // Add fragment title
911                SearchIndexableRaw data = new SearchIndexableRaw(context);
912                data.title = res.getString(R.string.wifi_settings);
913                data.screenTitle = res.getString(R.string.wifi_settings);
914                data.keywords = res.getString(R.string.keywords_wifi);
915                result.add(data);
916
917                // Add saved Wi-Fi access points
918                final Collection<AccessPoint> accessPoints =
919                        WifiTracker.getCurrentAccessPoints(context, true, false, false);
920                for (AccessPoint accessPoint : accessPoints) {
921                    data = new SearchIndexableRaw(context);
922                    data.title = accessPoint.getSsidStr();
923                    data.screenTitle = res.getString(R.string.wifi_settings);
924                    data.enabled = enabled;
925                    result.add(data);
926                }
927
928                return result;
929            }
930        };
931
932    /**
933     * Returns true if the config is not editable through Settings.
934     * @param context Context of caller
935     * @param config The WiFi config.
936     * @return true if the config is not editable through Settings.
937     */
938    static boolean isEditabilityLockedDown(Context context, WifiConfiguration config) {
939        return !canModifyNetwork(context, config);
940    }
941
942    /**
943     * This method is a stripped version of WifiConfigStore.canModifyNetwork.
944     * TODO: refactor to have only one method.
945     * @param context Context of caller
946     * @param config The WiFi config.
947     * @return true if Settings can modify the config.
948     */
949    static boolean canModifyNetwork(Context context, WifiConfiguration config) {
950        if (config == null) {
951            return true;
952        }
953
954        final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
955                Context.DEVICE_POLICY_SERVICE);
956
957        // Check if device has DPM capability. If it has and dpm is still null, then we
958        // treat this case with suspicion and bail out.
959        final PackageManager pm = context.getPackageManager();
960        if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) && dpm == null) {
961            return false;
962        }
963
964        boolean isConfigEligibleForLockdown = false;
965        if (dpm != null) {
966            final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnAnyUser();
967            if (deviceOwner != null) {
968                final int deviceOwnerUserId = dpm.getDeviceOwnerUserId();
969                try {
970                    final int deviceOwnerUid = pm.getPackageUidAsUser(deviceOwner.getPackageName(),
971                            deviceOwnerUserId);
972                    isConfigEligibleForLockdown = deviceOwnerUid == config.creatorUid;
973                } catch (NameNotFoundException e) {
974                    // don't care
975                }
976            }
977        }
978        if (!isConfigEligibleForLockdown) {
979            return true;
980        }
981
982        final ContentResolver resolver = context.getContentResolver();
983        final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
984                Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
985        return !isLockdownFeatureEnabled;
986    }
987
988    private static class SummaryProvider extends BroadcastReceiver
989            implements SummaryLoader.SummaryProvider {
990
991        private final Context mContext;
992        private final WifiManager mWifiManager;
993        private final WifiStatusTracker mWifiTracker;
994        private final SummaryLoader mSummaryLoader;
995
996        public SummaryProvider(Context context, SummaryLoader summaryLoader) {
997            mContext = context;
998            mSummaryLoader = summaryLoader;
999            mWifiManager = context.getSystemService(WifiManager.class);
1000            mWifiTracker = new WifiStatusTracker(mWifiManager);
1001        }
1002
1003        private CharSequence getSummary() {
1004            if (!mWifiTracker.enabled) {
1005                return mContext.getString(R.string.wifi_disabled_generic);
1006            }
1007            if (!mWifiTracker.connected) {
1008                return mContext.getString(R.string.disconnected);
1009            }
1010            return mWifiTracker.ssid;
1011        }
1012
1013        @Override
1014        public void setListening(boolean listening) {
1015            if (listening) {
1016                IntentFilter filter = new IntentFilter();
1017                filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
1018                filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
1019                filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
1020                mSummaryLoader.registerReceiver(this, filter);
1021            }
1022        }
1023
1024        @Override
1025        public void onReceive(Context context, Intent intent) {
1026            mWifiTracker.handleBroadcast(intent);
1027            mSummaryLoader.setSummary(this, getSummary());
1028        }
1029    }
1030
1031    public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
1032            = new SummaryLoader.SummaryProviderFactory() {
1033        @Override
1034        public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
1035                                                                   SummaryLoader summaryLoader) {
1036            return new SummaryProvider(activity, summaryLoader);
1037        }
1038    };
1039}
1040