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