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