WifiSettings.java revision 4ec4237b60f08db2e99e94b60752299007493785
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.PreferenceManager;
44import android.support.v7.preference.PreferenceViewHolder;
45import android.text.Spannable;
46import android.text.TextUtils;
47import android.text.style.TextAppearanceSpan;
48import android.util.Log;
49import android.view.ContextMenu;
50import android.view.ContextMenu.ContextMenuInfo;
51import android.view.Menu;
52import android.view.MenuInflater;
53import android.view.MenuItem;
54import android.view.View;
55import android.widget.ProgressBar;
56import android.widget.TextView;
57import android.widget.TextView.BufferType;
58import android.widget.Toast;
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;
76import java.util.ArrayList;
77import java.util.Collection;
78import java.util.List;
79
80import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
81
82/**
83 * Two types of UI are provided here.
84 *
85 * The first is for "usual Settings", appearing as any other Setup fragment.
86 *
87 * The second is for Setup Wizard, with a simplified interface that hides the action bar
88 * and menus.
89 */
90public class WifiSettings extends RestrictedSettingsFragment
91        implements Indexable, WifiTracker.WifiListener, AccessPointListener,
92        WifiDialog.WifiDialogListener {
93
94    private static final String TAG = "WifiSettings";
95
96    /* package */ static final int MENU_ID_WPS_PBC = Menu.FIRST;
97    private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1;
98    private static final int MENU_ID_ADVANCED = Menu.FIRST + 4;
99    private static final int MENU_ID_SCAN = Menu.FIRST + 5;
100    private static final int MENU_ID_CONNECT = Menu.FIRST + 6;
101    private static final int MENU_ID_FORGET = Menu.FIRST + 7;
102    private static final int MENU_ID_MODIFY = Menu.FIRST + 8;
103    private static final int MENU_ID_WRITE_NFC = Menu.FIRST + 9;
104    private static final int MENU_ID_CONFIGURE = Menu.FIRST + 10;
105
106    public static final int WIFI_DIALOG_ID = 1;
107    /* package */ static final int WPS_PBC_DIALOG_ID = 2;
108    private static final int WPS_PIN_DIALOG_ID = 3;
109    private static final int WRITE_NFC_DIALOG_ID = 6;
110
111    // Instance state keys
112    private static final String SAVE_DIALOG_MODE = "dialog_mode";
113    private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
114    private static final String SAVED_WIFI_NFC_DIALOG_STATE = "wifi_nfc_dlg_state";
115
116    protected WifiManager mWifiManager;
117    private WifiManager.ActionListener mConnectListener;
118    private WifiManager.ActionListener mSaveListener;
119    private WifiManager.ActionListener mForgetListener;
120
121    private WifiEnabler mWifiEnabler;
122    // An access point being editted is stored here.
123    private AccessPoint mSelectedAccessPoint;
124
125    private WifiDialog mDialog;
126    private WriteWifiConfigToNfcDialog mWifiToNfcDialog;
127
128    private ProgressBar mProgressHeader;
129
130    // this boolean extra specifies whether to disable the Next button when not connected. Used by
131    // account creation outside of setup wizard.
132    private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
133    // This string extra specifies a network to open the connect dialog on, so the user can enter
134    // network credentials.  This is used by quick settings for secured networks.
135    private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
136
137    // should Next button only be enabled when we have a connection?
138    private boolean mEnableNextOnConnection;
139
140    // Save the dialog details
141    private int mDialogMode;
142    private AccessPoint mDlgAccessPoint;
143    private Bundle mAccessPointSavedState;
144    private Bundle mWifiNfcDialogSavedState;
145
146    private WifiTracker mWifiTracker;
147    private String mOpenSsid;
148
149    private HandlerThread mBgThread;
150
151    private AccessPointPreference.UserBadgeCache mUserBadgeCache;
152    private Preference mAddPreference;
153
154    private MenuItem mScanMenuItem;
155
156    /* End of "used in Wifi Setup context" */
157
158    public WifiSettings() {
159        super(DISALLOW_CONFIG_WIFI);
160    }
161
162    @Override
163    public void onViewCreated(View view, Bundle savedInstanceState) {
164        super.onViewCreated(view, savedInstanceState);
165        final Activity activity = getActivity();
166        if (activity != null) {
167            mProgressHeader = (ProgressBar) setPinnedHeaderView(R.layout.wifi_progress_header);
168        }
169    }
170
171    @Override
172    public void onCreate(Bundle icicle) {
173        super.onCreate(icicle);
174        getPreferenceManager().setPreferenceComparisonCallback(
175                new PreferenceManager.SimplePreferenceComparisonCallback());
176        addPreferencesFromResource(R.xml.wifi_settings);
177        mAddPreference = new Preference(getContext());
178        mAddPreference.setIcon(R.drawable.ic_menu_add_inset);
179        mAddPreference.setTitle(R.string.wifi_add_network);
180
181        mUserBadgeCache = new AccessPointPreference.UserBadgeCache(getPackageManager());
182
183        mBgThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
184        mBgThread.start();
185    }
186
187    @Override
188    public void onDestroy() {
189        mBgThread.quit();
190        super.onDestroy();
191    }
192
193    @Override
194    public void onActivityCreated(Bundle savedInstanceState) {
195        super.onActivityCreated(savedInstanceState);
196
197        mWifiTracker =
198                new WifiTracker(getActivity(), this, mBgThread.getLooper(), true, true, false);
199        mWifiManager = mWifiTracker.getManager();
200
201        mConnectListener = new WifiManager.ActionListener() {
202                                   @Override
203                                   public void onSuccess() {
204                                   }
205                                   @Override
206                                   public void onFailure(int reason) {
207                                       Activity activity = getActivity();
208                                       if (activity != null) {
209                                           Toast.makeText(activity,
210                                                R.string.wifi_failed_connect_message,
211                                                Toast.LENGTH_SHORT).show();
212                                       }
213                                   }
214                               };
215
216        mSaveListener = new WifiManager.ActionListener() {
217                                @Override
218                                public void onSuccess() {
219                                }
220                                @Override
221                                public void onFailure(int reason) {
222                                    Activity activity = getActivity();
223                                    if (activity != null) {
224                                        Toast.makeText(activity,
225                                            R.string.wifi_failed_save_message,
226                                            Toast.LENGTH_SHORT).show();
227                                    }
228                                }
229                            };
230
231        mForgetListener = new WifiManager.ActionListener() {
232                                   @Override
233                                   public void onSuccess() {
234                                   }
235                                   @Override
236                                   public void onFailure(int reason) {
237                                       Activity activity = getActivity();
238                                       if (activity != null) {
239                                           Toast.makeText(activity,
240                                               R.string.wifi_failed_forget_message,
241                                               Toast.LENGTH_SHORT).show();
242                                       }
243                                   }
244                               };
245
246        if (savedInstanceState != null) {
247            mDialogMode = savedInstanceState.getInt(SAVE_DIALOG_MODE);
248            if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
249                mAccessPointSavedState =
250                    savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
251            }
252
253            if (savedInstanceState.containsKey(SAVED_WIFI_NFC_DIALOG_STATE)) {
254                mWifiNfcDialogSavedState =
255                    savedInstanceState.getBundle(SAVED_WIFI_NFC_DIALOG_STATE);
256            }
257        }
258
259        // if we're supposed to enable/disable the Next button based on our current connection
260        // state, start it off in the right state
261        Intent intent = getActivity().getIntent();
262        mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
263
264        if (mEnableNextOnConnection) {
265            if (hasNextButton()) {
266                final ConnectivityManager connectivity = (ConnectivityManager)
267                        getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
268                if (connectivity != null) {
269                    NetworkInfo info = connectivity.getNetworkInfo(
270                            ConnectivityManager.TYPE_WIFI);
271                    changeNextButtonState(info.isConnected());
272                }
273            }
274        }
275
276        registerForContextMenu(getListView());
277        setHasOptionsMenu(true);
278
279        if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) {
280            mOpenSsid = intent.getStringExtra(EXTRA_START_CONNECT_SSID);
281            onAccessPointsChanged();
282        }
283    }
284
285    @Override
286    public void onDestroyView() {
287        super.onDestroyView();
288
289        if (mWifiEnabler != null) {
290            mWifiEnabler.teardownSwitchBar();
291        }
292    }
293
294    @Override
295    public void onStart() {
296        super.onStart();
297
298        // On/off switch is hidden for Setup Wizard (returns null)
299        mWifiEnabler = createWifiEnabler();
300    }
301
302    /**
303     * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard)
304     */
305    /* package */ WifiEnabler createWifiEnabler() {
306        final SettingsActivity activity = (SettingsActivity) getActivity();
307        return new WifiEnabler(activity, activity.getSwitchBar());
308    }
309
310    @Override
311    public void onResume() {
312        final Activity activity = getActivity();
313        super.onResume();
314        removePreference("dummy");
315        if (mWifiEnabler != null) {
316            mWifiEnabler.resume(activity);
317        }
318
319        mWifiTracker.startTracking();
320        activity.invalidateOptionsMenu();
321    }
322
323    @Override
324    public void onPause() {
325        super.onPause();
326        if (mWifiEnabler != null) {
327            mWifiEnabler.pause();
328        }
329
330        mWifiTracker.stopTracking();
331    }
332
333    @Override
334    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
335        // If the user is not allowed to configure wifi, do not show the menu.
336        if (isUiRestricted()) return;
337
338        addOptionsMenuItems(menu);
339        super.onCreateOptionsMenu(menu, inflater);
340    }
341
342    /**
343     * @param menu
344     */
345    void addOptionsMenuItems(Menu menu) {
346        final boolean wifiIsEnabled = mWifiTracker.isWifiEnabled();
347        mScanMenuItem = menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.menu_stats_refresh);
348        mScanMenuItem.setEnabled(wifiIsEnabled)
349               .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
350        menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
351                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
352        menu.add(Menu.NONE, MENU_ID_CONFIGURE, 0, R.string.wifi_menu_configure)
353                .setIcon(R.drawable.ic_settings_24dp)
354                .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
355    }
356
357    @Override
358    protected int getMetricsCategory() {
359        return MetricsEvent.WIFI;
360    }
361
362    @Override
363    public void onSaveInstanceState(Bundle outState) {
364        super.onSaveInstanceState(outState);
365
366        // If the dialog is showing, save its state.
367        if (mDialog != null && mDialog.isShowing()) {
368            outState.putInt(SAVE_DIALOG_MODE, mDialogMode);
369            if (mDlgAccessPoint != null) {
370                mAccessPointSavedState = new Bundle();
371                mDlgAccessPoint.saveWifiState(mAccessPointSavedState);
372                outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
373            }
374        }
375
376        if (mWifiToNfcDialog != null && mWifiToNfcDialog.isShowing()) {
377            Bundle savedState = new Bundle();
378            mWifiToNfcDialog.saveState(savedState);
379            outState.putBundle(SAVED_WIFI_NFC_DIALOG_STATE, savedState);
380        }
381    }
382
383    @Override
384    public boolean onOptionsItemSelected(MenuItem item) {
385        // If the user is not allowed to configure wifi, do not handle menu selections.
386        if (isUiRestricted()) return false;
387
388        switch (item.getItemId()) {
389            case MENU_ID_WPS_PBC:
390                showDialog(WPS_PBC_DIALOG_ID);
391                return true;
392                /*
393            case MENU_ID_P2P:
394                if (getActivity() instanceof SettingsActivity) {
395                    ((SettingsActivity) getActivity()).startPreferencePanel(
396                            WifiP2pSettings.class.getCanonicalName(),
397                            null,
398                            R.string.wifi_p2p_settings_title, null,
399                            this, 0);
400                } else {
401                    startFragment(this, WifiP2pSettings.class.getCanonicalName(),
402                            R.string.wifi_p2p_settings_title, -1, null);
403                }
404                return true;
405                */
406            case MENU_ID_WPS_PIN:
407                showDialog(WPS_PIN_DIALOG_ID);
408                return true;
409            case MENU_ID_SCAN:
410                MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_FORCE_SCAN);
411                mWifiTracker.forceScan();
412                return true;
413            case MENU_ID_ADVANCED:
414                if (getActivity() instanceof SettingsActivity) {
415                    ((SettingsActivity) getActivity()).startPreferencePanel(
416                            AdvancedWifiSettings.class.getCanonicalName(), null,
417                            R.string.wifi_advanced_titlebar, null, this, 0);
418                } else {
419                    startFragment(this, AdvancedWifiSettings.class.getCanonicalName(),
420                            R.string.wifi_advanced_titlebar, -1 /* Do not request a results */,
421                            null);
422                }
423                return true;
424            case MENU_ID_CONFIGURE:
425                if (getActivity() instanceof SettingsActivity) {
426                    ((SettingsActivity) getActivity()).startPreferencePanel(
427                            ConfigureWifiSettings.class.getCanonicalName(), null,
428                            R.string.wifi_configure_titlebar, null, this, 0);
429                } else {
430                    startFragment(this, ConfigureWifiSettings.class.getCanonicalName(),
431                            R.string.wifi_configure_titlebar, -1 /* Do not request a results */,
432                            null);
433                }
434                return true;
435
436        }
437        return super.onOptionsItemSelected(item);
438    }
439
440    @Override
441    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
442            Preference preference = (Preference) view.getTag();
443
444            if (preference instanceof LongPressAccessPointPreference) {
445                mSelectedAccessPoint =
446                        ((LongPressAccessPointPreference) preference).getAccessPoint();
447                menu.setHeaderTitle(mSelectedAccessPoint.getSsid());
448                if (mSelectedAccessPoint.isConnectable()) {
449                    menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
450                }
451
452                WifiConfiguration config = mSelectedAccessPoint.getConfig();
453                // Some configs are ineditable
454                if (isEditabilityLockedDown(getActivity(), config)) {
455                    return;
456                }
457
458                if (mSelectedAccessPoint.isSaved() || mSelectedAccessPoint.isEphemeral()) {
459                    // Allow forgetting a network if either the network is saved or ephemerally
460                    // connected. (In the latter case, "forget" blacklists the network so it won't
461                    // be used again, ephemerally).
462                    menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
463                }
464                if (mSelectedAccessPoint.isSaved()) {
465                    menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
466                    NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
467                    if (nfcAdapter != null && nfcAdapter.isEnabled() &&
468                            mSelectedAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
469                        // Only allow writing of NFC tags for password-protected networks.
470                        menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc);
471                    }
472                }
473            }
474    }
475
476    @Override
477    public boolean onContextItemSelected(MenuItem item) {
478        if (mSelectedAccessPoint == null) {
479            return super.onContextItemSelected(item);
480        }
481        switch (item.getItemId()) {
482            case MENU_ID_CONNECT: {
483                if (mSelectedAccessPoint.isSaved()) {
484                    connect(mSelectedAccessPoint.getConfig());
485                } else if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE) {
486                    /** Bypass dialog for unsecured networks */
487                    mSelectedAccessPoint.generateOpenNetworkConfig();
488                    connect(mSelectedAccessPoint.getConfig());
489                } else {
490                    showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_CONNECT);
491                }
492                return true;
493            }
494            case MENU_ID_FORGET: {
495                forget();
496                return true;
497            }
498            case MENU_ID_MODIFY: {
499                showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_MODIFY);
500                return true;
501            }
502            case MENU_ID_WRITE_NFC:
503                showDialog(WRITE_NFC_DIALOG_ID);
504                return true;
505
506        }
507        return super.onContextItemSelected(item);
508    }
509
510    @Override
511    public boolean onPreferenceTreeClick(Preference preference) {
512        if (preference instanceof LongPressAccessPointPreference) {
513            mSelectedAccessPoint = ((LongPressAccessPointPreference) preference).getAccessPoint();
514            if (mSelectedAccessPoint == null) {
515                return false;
516            }
517            /** Bypass dialog for unsecured, unsaved, and inactive networks */
518            if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE &&
519                    !mSelectedAccessPoint.isSaved() && !mSelectedAccessPoint.isActive()) {
520                mSelectedAccessPoint.generateOpenNetworkConfig();
521                connect(mSelectedAccessPoint.getConfig());
522            } else if (mSelectedAccessPoint.isSaved()) {
523                showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_VIEW);
524            } else {
525                showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_CONNECT);
526            }
527        } else if (preference == mAddPreference) {
528            onAddNetworkPressed();
529        } else {
530            return super.onPreferenceTreeClick(preference);
531        }
532        return true;
533    }
534
535    private void showDialog(AccessPoint accessPoint, int dialogMode) {
536        if (accessPoint != null) {
537            WifiConfiguration config = accessPoint.getConfig();
538            if (isEditabilityLockedDown(getActivity(), config) && accessPoint.isActive()) {
539                RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(),
540                        RestrictedLockUtils.getDeviceOwner(getActivity()));
541                return;
542            }
543        }
544
545        if (mDialog != null) {
546            removeDialog(WIFI_DIALOG_ID);
547            mDialog = null;
548        }
549
550        // Save the access point and edit mode
551        mDlgAccessPoint = accessPoint;
552        mDialogMode = dialogMode;
553
554        showDialog(WIFI_DIALOG_ID);
555    }
556
557    @Override
558    public Dialog onCreateDialog(int dialogId) {
559        switch (dialogId) {
560            case WIFI_DIALOG_ID:
561                AccessPoint ap = mDlgAccessPoint; // For manual launch
562                if (ap == null) { // For re-launch from saved state
563                    if (mAccessPointSavedState != null) {
564                        ap = new AccessPoint(getActivity(), mAccessPointSavedState);
565                        // For repeated orientation changes
566                        mDlgAccessPoint = ap;
567                        // Reset the saved access point data
568                        mAccessPointSavedState = null;
569                    }
570                }
571                // If it's null, fine, it's for Add Network
572                mSelectedAccessPoint = ap;
573                mDialog = new WifiDialog(getActivity(), this, ap, mDialogMode,
574                        /* no hide submit/connect */ false);
575                return mDialog;
576            case WPS_PBC_DIALOG_ID:
577                return new WpsDialog(getActivity(), WpsInfo.PBC);
578            case WPS_PIN_DIALOG_ID:
579                return new WpsDialog(getActivity(), WpsInfo.DISPLAY);
580            case WRITE_NFC_DIALOG_ID:
581                if (mSelectedAccessPoint != null) {
582                    mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
583                            getActivity(), mSelectedAccessPoint.getConfig().networkId,
584                            mSelectedAccessPoint.getSecurity(),
585                            mWifiManager);
586                } else if (mWifiNfcDialogSavedState != null) {
587                    mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
588                            getActivity(), mWifiNfcDialogSavedState, mWifiManager);
589                }
590
591                return mWifiToNfcDialog;
592        }
593        return super.onCreateDialog(dialogId);
594    }
595
596    /**
597     * Shows the latest access points available with supplemental information like
598     * the strength of network and the security for it.
599     */
600    @Override
601    public void onAccessPointsChanged() {
602        // Safeguard from some delayed event handling
603        if (getActivity() == null) return;
604        if (isUiRestricted()) {
605            if (!isUiRestrictedByOnlyAdmin()) {
606                addMessagePreference(R.string.wifi_empty_list_user_restricted);
607            }
608            getPreferenceScreen().removeAll();
609            return;
610        }
611        final int wifiState = mWifiManager.getWifiState();
612
613        switch (wifiState) {
614            case WifiManager.WIFI_STATE_ENABLED:
615                // AccessPoints are automatically sorted with TreeSet.
616                final Collection<AccessPoint> accessPoints =
617                        mWifiTracker.getAccessPoints();
618
619                boolean hasAvailableAccessPoints = false;
620                int index = 0;
621                cacheRemoveAllPrefs(getPreferenceScreen());
622                for (AccessPoint accessPoint : accessPoints) {
623                    // Ignore access points that are out of range.
624                    if (accessPoint.getLevel() != -1) {
625                        String key = accessPoint.getBssid();
626                        if (TextUtils.isEmpty(key)) {
627                            key = accessPoint.getSsidStr();
628                        }
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,
639                                R.drawable.ic_wifi_signal_0, this);
640                        preference.setKey(key);
641                        preference.setOrder(index++);
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                        preference.refresh();
651                    }
652                }
653                removeCachedPrefs(getPreferenceScreen());
654                if (!hasAvailableAccessPoints) {
655                    setProgressBarVisible(true);
656                    Preference pref = new Preference(getContext()) {
657                        @Override
658                        public void onBindViewHolder(PreferenceViewHolder holder) {
659                            super.onBindViewHolder(holder);
660                            // Show a line on each side of add network.
661                            holder.setDividerAllowedBelow(true);
662                        }
663                    };
664                    pref.setSelectable(false);
665                    pref.setSummary(R.string.wifi_empty_list_wifi_on);
666                    pref.setOrder(0);
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_user_restricted);
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 ? 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());
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);
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 {
842            mWifiManager.forget(mSelectedAccessPoint.getConfig().networkId, mForgetListener);
843        }
844
845        mWifiTracker.resumeScanning();
846
847        // We need to rename/replace "Next" button in wifi setup context.
848        changeNextButtonState(false);
849    }
850
851    protected void connect(final WifiConfiguration config) {
852        MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT);
853        mWifiManager.connect(config, mConnectListener);
854    }
855
856    protected void connect(final int networkId) {
857        MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT);
858        mWifiManager.connect(networkId, mConnectListener);
859    }
860
861    /**
862     * Called when "add network" button is pressed.
863     */
864    /* package */ void onAddNetworkPressed() {
865        MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_ADD_NETWORK);
866        // No exact access point is selected.
867        mSelectedAccessPoint = null;
868        showDialog(null, WifiConfigUiBase.MODE_CONNECT);
869    }
870
871    @Override
872    protected int getHelpResource() {
873        return R.string.help_url_wifi;
874    }
875
876    @Override
877    public void onAccessPointChanged(AccessPoint accessPoint) {
878        ((LongPressAccessPointPreference) accessPoint.getTag()).refresh();
879    }
880
881    @Override
882    public void onLevelChanged(AccessPoint accessPoint) {
883        ((LongPressAccessPointPreference) accessPoint.getTag()).onLevelChanged();
884    }
885
886    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
887        new BaseSearchIndexProvider() {
888            @Override
889            public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
890                final List<SearchIndexableRaw> result = new ArrayList<>();
891                final Resources res = context.getResources();
892
893                // Add fragment title
894                SearchIndexableRaw data = new SearchIndexableRaw(context);
895                data.title = res.getString(R.string.wifi_settings);
896                data.screenTitle = res.getString(R.string.wifi_settings);
897                data.keywords = res.getString(R.string.keywords_wifi);
898                result.add(data);
899
900                // Add saved Wi-Fi access points
901                final Collection<AccessPoint> accessPoints =
902                        WifiTracker.getCurrentAccessPoints(context, true, false, false);
903                for (AccessPoint accessPoint : accessPoints) {
904                    data = new SearchIndexableRaw(context);
905                    data.title = accessPoint.getSsidStr();
906                    data.screenTitle = res.getString(R.string.wifi_settings);
907                    data.enabled = enabled;
908                    result.add(data);
909                }
910
911                return result;
912            }
913        };
914
915    /**
916     * Returns true if the config is not editable through Settings.
917     * @param context Context of caller
918     * @param config The WiFi config.
919     * @return true if the config is not editable through Settings.
920     */
921    static boolean isEditabilityLockedDown(Context context, WifiConfiguration config) {
922        return !canModifyNetwork(context, config);
923    }
924
925    /**
926     * This method is a stripped version of WifiConfigStore.canModifyNetwork.
927     * TODO: refactor to have only one method.
928     * @param context Context of caller
929     * @param config The WiFi config.
930     * @return true if Settings can modify the config.
931     */
932    static boolean canModifyNetwork(Context context, WifiConfiguration config) {
933        if (config == null) {
934            return true;
935        }
936
937        final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
938                Context.DEVICE_POLICY_SERVICE);
939
940        // Check if device has DPM capability. If it has and dpm is still null, then we
941        // treat this case with suspicion and bail out.
942        final PackageManager pm = context.getPackageManager();
943        if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) && dpm == null) {
944            return false;
945        }
946
947        boolean isConfigEligibleForLockdown = false;
948        if (dpm != null) {
949            final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnAnyUser();
950            if (deviceOwner != null) {
951                final int deviceOwnerUserId = dpm.getDeviceOwnerUserId();
952                try {
953                    final int deviceOwnerUid = pm.getPackageUidAsUser(deviceOwner.getPackageName(),
954                            deviceOwnerUserId);
955                    isConfigEligibleForLockdown = deviceOwnerUid == config.creatorUid;
956                } catch (NameNotFoundException e) {
957                    // don't care
958                }
959            }
960        }
961        if (!isConfigEligibleForLockdown) {
962            return true;
963        }
964
965        final ContentResolver resolver = context.getContentResolver();
966        final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
967                Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
968        return !isLockdownFeatureEnabled;
969    }
970
971    private static class SummaryProvider extends BroadcastReceiver
972            implements SummaryLoader.SummaryProvider {
973
974        private final Context mContext;
975        private final WifiManager mWifiManager;
976        private final WifiStatusTracker mWifiTracker;
977        private final SummaryLoader mSummaryLoader;
978
979        public SummaryProvider(Context context, SummaryLoader summaryLoader) {
980            mContext = context;
981            mSummaryLoader = summaryLoader;
982            mWifiManager = context.getSystemService(WifiManager.class);
983            mWifiTracker = new WifiStatusTracker(mWifiManager);
984        }
985
986        private CharSequence getSummary() {
987            if (!mWifiTracker.enabled) {
988                return mContext.getString(R.string.wifi_disabled_generic);
989            }
990            if (!mWifiTracker.connected) {
991                return mContext.getString(R.string.disconnected);
992            }
993            return mWifiTracker.ssid;
994        }
995
996        @Override
997        public void setListening(boolean listening) {
998            if (listening) {
999                IntentFilter filter = new IntentFilter();
1000                filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
1001                filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
1002                filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
1003                mSummaryLoader.registerReceiver(this, filter);
1004            }
1005        }
1006
1007        @Override
1008        public void onReceive(Context context, Intent intent) {
1009            mWifiTracker.handleBroadcast(intent);
1010            mSummaryLoader.setSummary(this, getSummary());
1011        }
1012    }
1013
1014    public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
1015            = new SummaryLoader.SummaryProviderFactory() {
1016        @Override
1017        public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
1018                                                                   SummaryLoader summaryLoader) {
1019            return new SummaryProvider(activity, summaryLoader);
1020        }
1021    };
1022}
1023