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