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