WifiSettings.java revision c6ca314c0b872f75926807ca7d6eb24ebe7cb684
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        mConnectedAccessPointPreferenceCategory.setVisible(false); // initially hidden
198
199        mAccessPointsPreferenceCategory =
200                (PreferenceCategory) findPreference(PREF_KEY_ACCESS_POINTS);
201        mAdditionalSettingsPreferenceCategory =
202                (PreferenceCategory) findPreference(PREF_KEY_ADDITIONAL_SETTINGS);
203        mConfigureWifiSettingsPreference = findPreference(PREF_KEY_CONFIGURE_WIFI_SETTINGS);
204        mSavedNetworksPreference = findPreference(PREF_KEY_SAVED_NETWORKS);
205
206        if (isUiRestricted()) {
207            getPreferenceScreen().removePreference(mAdditionalSettingsPreferenceCategory);
208        }
209
210        Context prefContext = getPrefContext();
211        mAddPreference = new Preference(prefContext);
212        mAddPreference.setIcon(R.drawable.ic_menu_add_inset);
213        mAddPreference.setTitle(R.string.wifi_add_network);
214        mSeeAllNetworksPreference = new Preference(prefContext);
215        mSeeAllNetworksPreference.setIcon(R.drawable.ic_arrow_down_24dp);
216        mSeeAllNetworksPreference.setTitle(R.string.wifi_see_all_networks_button_title);
217        mSeeAllNetworks = false;
218        mStatusMessagePreference = new LinkablePreference(prefContext);
219
220        mUserBadgeCache = new AccessPointPreference.UserBadgeCache(getPackageManager());
221
222        mBgThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
223        mBgThread.start();
224    }
225
226    @Override
227    public void onDestroy() {
228        mBgThread.quit();
229        super.onDestroy();
230    }
231
232    @Override
233    public void onActivityCreated(Bundle savedInstanceState) {
234        super.onActivityCreated(savedInstanceState);
235
236        mWifiTracker =
237                new WifiTracker(getActivity(), this, mBgThread.getLooper(), true, true, false);
238        mWifiManager = mWifiTracker.getManager();
239
240        mConnectListener = new WifiManager.ActionListener() {
241                                   @Override
242                                   public void onSuccess() {
243                                   }
244                                   @Override
245                                   public void onFailure(int reason) {
246                                       Activity activity = getActivity();
247                                       if (activity != null) {
248                                           Toast.makeText(activity,
249                                                R.string.wifi_failed_connect_message,
250                                                Toast.LENGTH_SHORT).show();
251                                       }
252                                   }
253                               };
254
255        mSaveListener = new WifiManager.ActionListener() {
256                                @Override
257                                public void onSuccess() {
258                                }
259                                @Override
260                                public void onFailure(int reason) {
261                                    Activity activity = getActivity();
262                                    if (activity != null) {
263                                        Toast.makeText(activity,
264                                            R.string.wifi_failed_save_message,
265                                            Toast.LENGTH_SHORT).show();
266                                    }
267                                }
268                            };
269
270        mForgetListener = new WifiManager.ActionListener() {
271                                   @Override
272                                   public void onSuccess() {
273                                   }
274                                   @Override
275                                   public void onFailure(int reason) {
276                                       Activity activity = getActivity();
277                                       if (activity != null) {
278                                           Toast.makeText(activity,
279                                               R.string.wifi_failed_forget_message,
280                                               Toast.LENGTH_SHORT).show();
281                                       }
282                                   }
283                               };
284
285        if (savedInstanceState != null) {
286            mDialogMode = savedInstanceState.getInt(SAVE_DIALOG_MODE);
287            if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
288                mAccessPointSavedState =
289                    savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
290            }
291
292            if (savedInstanceState.containsKey(SAVED_WIFI_NFC_DIALOG_STATE)) {
293                mWifiNfcDialogSavedState =
294                    savedInstanceState.getBundle(SAVED_WIFI_NFC_DIALOG_STATE);
295            }
296        }
297
298        // if we're supposed to enable/disable the Next button based on our current connection
299        // state, start it off in the right state
300        Intent intent = getActivity().getIntent();
301        mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
302
303        if (mEnableNextOnConnection) {
304            if (hasNextButton()) {
305                final ConnectivityManager connectivity = (ConnectivityManager)
306                        getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
307                if (connectivity != null) {
308                    NetworkInfo info = connectivity.getNetworkInfo(
309                            ConnectivityManager.TYPE_WIFI);
310                    changeNextButtonState(info.isConnected());
311                }
312            }
313        }
314
315        registerForContextMenu(getListView());
316        setHasOptionsMenu(true);
317
318        if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) {
319            mOpenSsid = intent.getStringExtra(EXTRA_START_CONNECT_SSID);
320            onAccessPointsChanged();
321        }
322    }
323
324    @Override
325    public void onDestroyView() {
326        super.onDestroyView();
327
328        if (mWifiEnabler != null) {
329            mWifiEnabler.teardownSwitchController();
330        }
331    }
332
333    @Override
334    public void onStart() {
335        super.onStart();
336
337        // On/off switch is hidden for Setup Wizard (returns null)
338        mWifiEnabler = createWifiEnabler();
339    }
340
341    /**
342     * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard)
343     */
344    private WifiEnabler createWifiEnabler() {
345        final SettingsActivity activity = (SettingsActivity) getActivity();
346        return new WifiEnabler(activity, new SwitchBarController(activity.getSwitchBar()),
347            mMetricsFeatureProvider);
348    }
349
350    @Override
351    public void onResume() {
352        final Activity activity = getActivity();
353        super.onResume();
354        if (mWifiEnabler != null) {
355            mWifiEnabler.resume(activity);
356        }
357
358        mWifiTracker.startTracking();
359        activity.invalidateOptionsMenu();
360    }
361
362    @Override
363    public void onPause() {
364        super.onPause();
365        if (mWifiEnabler != null) {
366            mWifiEnabler.pause();
367        }
368
369        mWifiTracker.stopTracking();
370    }
371
372    @Override
373    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
374        // If the user is not allowed to configure wifi, do not show the menu.
375        if (isUiRestricted()) return;
376
377        addOptionsMenuItems(menu);
378        super.onCreateOptionsMenu(menu, inflater);
379    }
380
381    /**
382     * @param menu
383     */
384    void addOptionsMenuItems(Menu menu) {
385        final boolean wifiIsEnabled = mWifiTracker.isWifiEnabled();
386        mScanMenuItem = menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.menu_stats_refresh)
387                .setIcon(com.android.internal.R.drawable.ic_menu_refresh);
388        mScanMenuItem.setEnabled(wifiIsEnabled)
389                .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
390    }
391
392    @Override
393    public int getMetricsCategory() {
394        return MetricsEvent.WIFI;
395    }
396
397    @Override
398    public void onSaveInstanceState(Bundle outState) {
399        super.onSaveInstanceState(outState);
400
401        // If the dialog is showing, save its state.
402        if (mDialog != null && mDialog.isShowing()) {
403            outState.putInt(SAVE_DIALOG_MODE, mDialogMode);
404            if (mDlgAccessPoint != null) {
405                mAccessPointSavedState = new Bundle();
406                mDlgAccessPoint.saveWifiState(mAccessPointSavedState);
407                outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
408            }
409        }
410
411        if (mWifiToNfcDialog != null && mWifiToNfcDialog.isShowing()) {
412            Bundle savedState = new Bundle();
413            mWifiToNfcDialog.saveState(savedState);
414            outState.putBundle(SAVED_WIFI_NFC_DIALOG_STATE, savedState);
415        }
416    }
417
418    @Override
419    public boolean onOptionsItemSelected(MenuItem item) {
420        // If the user is not allowed to configure wifi, do not handle menu selections.
421        if (isUiRestricted()) return false;
422
423        switch (item.getItemId()) {
424            case MENU_ID_WPS_PBC:
425                showDialog(WPS_PBC_DIALOG_ID);
426                return true;
427                /*
428            case MENU_ID_P2P:
429                if (getActivity() instanceof SettingsActivity) {
430                    ((SettingsActivity) getActivity()).startPreferencePanel(
431                            WifiP2pSettings.class.getCanonicalName(),
432                            null,
433                            R.string.wifi_p2p_settings_title, null,
434                            this, 0);
435                } else {
436                    startFragment(this, WifiP2pSettings.class.getCanonicalName(),
437                            R.string.wifi_p2p_settings_title, -1, null);
438                }
439                return true;
440                */
441            case MENU_ID_WPS_PIN:
442                showDialog(WPS_PIN_DIALOG_ID);
443                return true;
444            case MENU_ID_SCAN:
445                mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_FORCE_SCAN);
446                mWifiTracker.forceScan();
447                return true;
448        }
449        return super.onOptionsItemSelected(item);
450    }
451
452    @Override
453    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
454            Preference preference = (Preference) view.getTag();
455
456            if (preference instanceof LongPressAccessPointPreference) {
457                mSelectedAccessPoint =
458                        ((LongPressAccessPointPreference) preference).getAccessPoint();
459                menu.setHeaderTitle(mSelectedAccessPoint.getSsid());
460                if (mSelectedAccessPoint.isConnectable()) {
461                    menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
462                }
463
464                WifiConfiguration config = mSelectedAccessPoint.getConfig();
465                // Some configs are ineditable
466                if (isEditabilityLockedDown(getActivity(), config)) {
467                    return;
468                }
469
470                if (mSelectedAccessPoint.isSaved() || mSelectedAccessPoint.isEphemeral()) {
471                    // Allow forgetting a network if either the network is saved or ephemerally
472                    // connected. (In the latter case, "forget" blacklists the network so it won't
473                    // be used again, ephemerally).
474                    menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
475                }
476                if (mSelectedAccessPoint.isSaved()) {
477                    menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
478                    NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
479                    if (nfcAdapter != null && nfcAdapter.isEnabled() &&
480                            mSelectedAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
481                        // Only allow writing of NFC tags for password-protected networks.
482                        menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc);
483                    }
484                }
485            }
486    }
487
488    @Override
489    public boolean onContextItemSelected(MenuItem item) {
490        if (mSelectedAccessPoint == null) {
491            return super.onContextItemSelected(item);
492        }
493        switch (item.getItemId()) {
494            case MENU_ID_CONNECT: {
495                boolean isSavedNetwork = mSelectedAccessPoint.isSaved();
496                if (isSavedNetwork) {
497                    connect(mSelectedAccessPoint.getConfig(), isSavedNetwork);
498                } else if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE) {
499                    /** Bypass dialog for unsecured networks */
500                    mSelectedAccessPoint.generateOpenNetworkConfig();
501                    connect(mSelectedAccessPoint.getConfig(), isSavedNetwork);
502                } else {
503                    showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_CONNECT);
504                }
505                return true;
506            }
507            case MENU_ID_FORGET: {
508                forget();
509                return true;
510            }
511            case MENU_ID_MODIFY: {
512                showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_MODIFY);
513                return true;
514            }
515            case MENU_ID_WRITE_NFC:
516                showDialog(WRITE_NFC_DIALOG_ID);
517                return true;
518
519        }
520        return super.onContextItemSelected(item);
521    }
522
523    @Override
524    public boolean onPreferenceTreeClick(Preference preference) {
525        if (preference instanceof LongPressAccessPointPreference) {
526            mSelectedAccessPoint = ((LongPressAccessPointPreference) preference).getAccessPoint();
527            if (mSelectedAccessPoint == null) {
528                return false;
529            }
530            /** Bypass dialog for unsecured, unsaved, and inactive networks */
531            if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE &&
532                    !mSelectedAccessPoint.isSaved() && !mSelectedAccessPoint.isActive()) {
533                mSelectedAccessPoint.generateOpenNetworkConfig();
534                connect(mSelectedAccessPoint.getConfig(), false /* isSavedNetwork */);
535            } else if (mSelectedAccessPoint.isSaved()) {
536                showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_VIEW);
537            } else {
538                showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_CONNECT);
539            }
540        } else if (preference == mAddPreference) {
541            onAddNetworkPressed();
542        } else if (preference == mSeeAllNetworksPreference) {
543            mSeeAllNetworks = true;
544            onAccessPointsChanged();
545        } else {
546            return super.onPreferenceTreeClick(preference);
547        }
548        return true;
549    }
550
551    private void showDialog(AccessPoint accessPoint, int dialogMode) {
552        if (accessPoint != null) {
553            WifiConfiguration config = accessPoint.getConfig();
554            if (isEditabilityLockedDown(getActivity(), config) && accessPoint.isActive()) {
555                RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(),
556                        RestrictedLockUtils.getDeviceOwner(getActivity()));
557                return;
558            }
559        }
560
561        if (mDialog != null) {
562            removeDialog(WIFI_DIALOG_ID);
563            mDialog = null;
564        }
565
566        // Save the access point and edit mode
567        mDlgAccessPoint = accessPoint;
568        mDialogMode = dialogMode;
569
570        showDialog(WIFI_DIALOG_ID);
571    }
572
573    @Override
574    public Dialog onCreateDialog(int dialogId) {
575        switch (dialogId) {
576            case WIFI_DIALOG_ID:
577                AccessPoint ap = mDlgAccessPoint; // For manual launch
578                if (ap == null) { // For re-launch from saved state
579                    if (mAccessPointSavedState != null) {
580                        ap = new AccessPoint(getActivity(), mAccessPointSavedState);
581                        // For repeated orientation changes
582                        mDlgAccessPoint = ap;
583                        // Reset the saved access point data
584                        mAccessPointSavedState = null;
585                    }
586                }
587                // If it's null, fine, it's for Add Network
588                mSelectedAccessPoint = ap;
589                mDialog = new WifiDialog(getActivity(), this, ap, mDialogMode,
590                        /* no hide submit/connect */ false);
591                return mDialog;
592            case WPS_PBC_DIALOG_ID:
593                return new WpsDialog(getActivity(), WpsInfo.PBC);
594            case WPS_PIN_DIALOG_ID:
595                return new WpsDialog(getActivity(), WpsInfo.DISPLAY);
596            case WRITE_NFC_DIALOG_ID:
597                if (mSelectedAccessPoint != null) {
598                    mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
599                            getActivity(), mSelectedAccessPoint.getConfig().networkId,
600                            mSelectedAccessPoint.getSecurity(),
601                            mWifiManager);
602                } else if (mWifiNfcDialogSavedState != null) {
603                    mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
604                            getActivity(), mWifiNfcDialogSavedState, mWifiManager);
605                }
606
607                return mWifiToNfcDialog;
608        }
609        return super.onCreateDialog(dialogId);
610    }
611
612    @Override
613    public int getDialogMetricsCategory(int dialogId) {
614        switch (dialogId) {
615            case WIFI_DIALOG_ID:
616                return MetricsEvent.DIALOG_WIFI_AP_EDIT;
617            case WPS_PBC_DIALOG_ID:
618                return MetricsEvent.DIALOG_WIFI_PBC;
619            case WPS_PIN_DIALOG_ID:
620                return MetricsEvent.DIALOG_WIFI_PIN;
621            case WRITE_NFC_DIALOG_ID:
622                return MetricsEvent.DIALOG_WIFI_WRITE_NFC;
623            default:
624                return 0;
625        }
626    }
627
628    /**
629     * Shows the latest access points available with supplemental information like
630     * the strength of network and the security for it.
631     */
632    @Override
633    public void onAccessPointsChanged() {
634        // Safeguard from some delayed event handling
635        if (getActivity() == null) return;
636        if (isUiRestricted()) {
637            removeConnectedAccessPointPreference();
638            mAccessPointsPreferenceCategory.removeAll();
639            if (!isUiRestrictedByOnlyAdmin()) {
640                addMessagePreference(R.string.wifi_empty_list_user_restricted);
641            }
642            return;
643        }
644        final int wifiState = mWifiManager.getWifiState();
645
646        switch (wifiState) {
647            case WifiManager.WIFI_STATE_ENABLED:
648                // AccessPoints are sorted by the WifiTracker
649                final List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints();
650
651                boolean hasAvailableAccessPoints = false;
652                mAccessPointsPreferenceCategory.removePreference(mStatusMessagePreference);
653                cacheRemoveAllPrefs(mAccessPointsPreferenceCategory);
654
655                int index = configureConnectedAccessPointPreferenceCategory(accessPoints) ? 1 : 0;
656                boolean fewerNetworksThanLimit =
657                        accessPoints.size() <= index + NETWORKS_TO_INITIALLY_SHOW;
658                int numAccessPointsToShow = mSeeAllNetworks || fewerNetworksThanLimit
659                        ? accessPoints.size() : index + NETWORKS_TO_INITIALLY_SHOW;
660
661                for (; index < numAccessPointsToShow; index++) {
662                    AccessPoint accessPoint = accessPoints.get(index);
663                    // Ignore access points that are out of range.
664                    if (accessPoint.getLevel() != -1) {
665                        String key = accessPoint.getBssid();
666                        if (TextUtils.isEmpty(key)) {
667                            key = accessPoint.getSsidStr();
668                        }
669                        hasAvailableAccessPoints = true;
670                        LongPressAccessPointPreference pref = (LongPressAccessPointPreference)
671                                getCachedPreference(key);
672                        if (pref != null) {
673                            pref.setOrder(index);
674                            continue;
675                        }
676                        LongPressAccessPointPreference
677                                preference = createLongPressActionPointPreference(accessPoint);
678                        preference.setKey(key);
679                        preference.setOrder(index);
680                        if (mOpenSsid != null && mOpenSsid.equals(accessPoint.getSsidStr())
681                                && !accessPoint.isSaved()
682                                && accessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
683                            onPreferenceTreeClick(preference);
684                            mOpenSsid = null;
685                        }
686                        mAccessPointsPreferenceCategory.addPreference(preference);
687                        accessPoint.setListener(this);
688                        preference.refresh();
689                    }
690                }
691                removeCachedPrefs(mAccessPointsPreferenceCategory);
692                if (!hasAvailableAccessPoints) {
693                    setProgressBarVisible(true);
694                    Preference pref = new Preference(getContext()) {
695                        @Override
696                        public void onBindViewHolder(PreferenceViewHolder holder) {
697                            super.onBindViewHolder(holder);
698                            // Show a line on each side of add network.
699                            holder.setDividerAllowedBelow(true);
700                        }
701                    };
702                    pref.setSelectable(false);
703                    pref.setSummary(R.string.wifi_empty_list_wifi_on);
704                    pref.setOrder(index++);
705                    pref.setKey(PREF_KEY_EMPTY_WIFI_LIST);
706                    mAccessPointsPreferenceCategory.addPreference(pref);
707                } else {
708                    setProgressBarVisible(false);
709                }
710                if (mSeeAllNetworks || fewerNetworksThanLimit) {
711                    mAccessPointsPreferenceCategory.removePreference(mSeeAllNetworksPreference);
712                    mAddPreference.setOrder(index);
713                    mAccessPointsPreferenceCategory.addPreference(mAddPreference);
714                } else {
715                    mAccessPointsPreferenceCategory.removePreference(mAddPreference);
716                    mSeeAllNetworksPreference.setOrder(index);
717                    mAccessPointsPreferenceCategory.addPreference(mSeeAllNetworksPreference);
718                }
719                setConfigureWifiSettingsVisibility();
720                if (mScanMenuItem != null) {
721                    mScanMenuItem.setEnabled(true);
722                }
723                break;
724
725            case WifiManager.WIFI_STATE_ENABLING:
726                removeConnectedAccessPointPreference();
727                mAccessPointsPreferenceCategory.removeAll();
728                setProgressBarVisible(true);
729                break;
730
731            case WifiManager.WIFI_STATE_DISABLING:
732                addMessagePreference(R.string.wifi_stopping);
733                setProgressBarVisible(true);
734                break;
735
736            case WifiManager.WIFI_STATE_DISABLED:
737                setOffMessage();
738                setConfigureWifiSettingsVisibility();
739                setProgressBarVisible(false);
740                if (mScanMenuItem != null) {
741                    mScanMenuItem.setEnabled(false);
742                }
743                break;
744        }
745    }
746
747    @NonNull
748    private LongPressAccessPointPreference createLongPressActionPointPreference(
749            AccessPoint accessPoint) {
750        return new LongPressAccessPointPreference(accessPoint, getPrefContext(), mUserBadgeCache,
751                false, R.drawable.ic_wifi_signal_0, this);
752    }
753
754    /**
755     * Configure the ConnectedAccessPointPreferenceCategory and return true if the Category was
756     * shown.
757     */
758    private boolean configureConnectedAccessPointPreferenceCategory(
759            List<AccessPoint> accessPoints) {
760        if (accessPoints.size() == 0) {
761            removeConnectedAccessPointPreference();
762            return false;
763        }
764
765        AccessPoint connectedAp = accessPoints.get(0);
766        if (!connectedAp.isActive()) {
767            removeConnectedAccessPointPreference();
768            return false;
769        }
770
771        // Is the preference category empty?
772        if (mConnectedAccessPointPreferenceCategory.getPreferenceCount() == 0) {
773            addConnectedAccessPointPreference(connectedAp);
774            return true;
775        }
776
777        // Is the previous currently connected SSID different from the new one?
778        if (!((AccessPointPreference)
779                mConnectedAccessPointPreferenceCategory.getPreference(0))
780                        .getAccessPoint().getSsidStr().equals(
781                                connectedAp.getSsidStr())) {
782            removeConnectedAccessPointPreference();
783            addConnectedAccessPointPreference(connectedAp);
784            return true;
785        }
786
787        // Else same AP is connected, nothing to do
788        return true;
789    }
790
791    /**
792     * Creates a Preference for the given {@link AccessPoint} and adds it to the
793     * {@link #mConnectedAccessPointPreferenceCategory}.
794     */
795    private void addConnectedAccessPointPreference(AccessPoint connectedAp) {
796        String key = connectedAp.getBssid();
797        LongPressAccessPointPreference pref = (LongPressAccessPointPreference)
798                getCachedPreference(key);
799        if (pref == null) {
800            pref = createLongPressActionPointPreference(connectedAp);
801        }
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