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