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