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