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