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