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