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