WifiSettings.java revision 3d32af264254660150c5f2d63a64b3462a9378dd
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 static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID;
20import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
21
22import android.app.Activity;
23import android.app.AlertDialog;
24import android.app.AppGlobals;
25import android.app.Dialog;
26import android.app.admin.DevicePolicyManager;
27import android.content.Context;
28import android.content.DialogInterface;
29import android.content.Intent;
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.content.res.TypedArray;
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.os.UserManager;
49import android.preference.Preference;
50import android.preference.PreferenceScreen;
51import android.text.Spannable;
52import android.text.style.TextAppearanceSpan;
53import android.util.Log;
54import android.view.ContextMenu;
55import android.view.ContextMenu.ContextMenuInfo;
56import android.view.Gravity;
57import android.view.Menu;
58import android.view.MenuInflater;
59import android.view.MenuItem;
60import android.view.View;
61import android.widget.AdapterView.AdapterContextMenuInfo;
62import android.widget.ProgressBar;
63import android.widget.TextView;
64import android.widget.TextView.BufferType;
65import android.widget.Toast;
66
67import com.android.internal.logging.MetricsLogger;
68import com.android.settings.LinkifyUtils;
69import com.android.settings.R;
70import com.android.settings.RestrictedSettingsFragment;
71import com.android.settings.SettingsActivity;
72import com.android.settings.location.ScanningSettings;
73import com.android.settings.search.BaseSearchIndexProvider;
74import com.android.settings.search.Indexable;
75import com.android.settings.search.SearchIndexableRaw;
76import com.android.settingslib.wifi.AccessPoint;
77import com.android.settingslib.wifi.AccessPoint.AccessPointListener;
78import com.android.settingslib.wifi.WifiTracker;
79
80import java.util.ArrayList;
81import java.util.Collection;
82import java.util.List;
83
84/**
85 * Two types of UI are provided here.
86 *
87 * The first is for "usual Settings", appearing as any other Setup fragment.
88 *
89 * The second is for Setup Wizard, with a simplified interface that hides the action bar
90 * and menus.
91 */
92public class WifiSettings extends RestrictedSettingsFragment
93        implements DialogInterface.OnClickListener, Indexable, WifiTracker.WifiListener,
94        AccessPointListener {
95
96    private static final String TAG = "WifiSettings";
97
98    /* package */ static final int MENU_ID_WPS_PBC = Menu.FIRST;
99    private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1;
100    private static final int MENU_ID_SAVED_NETWORK = Menu.FIRST + 2;
101    /* package */ static final int MENU_ID_ADD_NETWORK = Menu.FIRST + 3;
102    private static final int MENU_ID_ADVANCED = Menu.FIRST + 4;
103    private static final int MENU_ID_SCAN = Menu.FIRST + 5;
104    private static final int MENU_ID_CONNECT = Menu.FIRST + 6;
105    private static final int MENU_ID_FORGET = Menu.FIRST + 7;
106    private static final int MENU_ID_MODIFY = Menu.FIRST + 8;
107    private static final int MENU_ID_WRITE_NFC = Menu.FIRST + 9;
108
109    public static final int WIFI_DIALOG_ID = 1;
110    /* package */ static final int WPS_PBC_DIALOG_ID = 2;
111    private static final int WPS_PIN_DIALOG_ID = 3;
112    private static final int WRITE_NFC_DIALOG_ID = 6;
113
114    // Instance state keys
115    private static final String SAVE_DIALOG_EDIT_MODE = "edit_mode";
116    private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
117    private static final String SAVED_WIFI_NFC_DIALOG_STATE = "wifi_nfc_dlg_state";
118
119    private static boolean savedNetworksExist;
120
121    protected WifiManager mWifiManager;
122    private WifiManager.ActionListener mConnectListener;
123    private WifiManager.ActionListener mSaveListener;
124    private WifiManager.ActionListener mForgetListener;
125
126    private WifiEnabler mWifiEnabler;
127    // An access point being editted is stored here.
128    private AccessPoint mSelectedAccessPoint;
129
130    private WifiDialog mDialog;
131    private WriteWifiConfigToNfcDialog mWifiToNfcDialog;
132
133    private TextView mEmptyView;
134    private ProgressBar mProgressHeader;
135
136    // this boolean extra specifies whether to disable the Next button when not connected. Used by
137    // account creation outside of setup wizard.
138    private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
139    // This string extra specifies a network to open the connect dialog on, so the user can enter
140    // network credentials.  This is used by quick settings for secured networks.
141    private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
142
143    // should Next button only be enabled when we have a connection?
144    private boolean mEnableNextOnConnection;
145
146    // Save the dialog details
147    private boolean mDlgEdit;
148    private AccessPoint mDlgAccessPoint;
149    private Bundle mAccessPointSavedState;
150    private Bundle mWifiNfcDialogSavedState;
151
152    private WifiTracker mWifiTracker;
153    private String mOpenSsid;
154
155    private HandlerThread mBgThread;
156
157    /* End of "used in Wifi Setup context" */
158
159    public WifiSettings() {
160        super(DISALLOW_CONFIG_WIFI);
161    }
162
163    @Override
164    public void onViewCreated(View view, Bundle savedInstanceState) {
165        super.onViewCreated(view, savedInstanceState);
166        final Activity activity = getActivity();
167        if (activity != null) {
168            mProgressHeader = (ProgressBar) setPinnedHeaderView(R.layout.wifi_progress_header);
169        }
170    }
171
172    @Override
173    public void onCreate(Bundle icicle) {
174        super.onCreate(icicle);
175        mBgThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
176        mBgThread.start();
177    }
178
179    @Override
180    public void onDestroy() {
181        mBgThread.quit();
182        super.onDestroy();
183    }
184
185    @Override
186    public void onActivityCreated(Bundle savedInstanceState) {
187        super.onActivityCreated(savedInstanceState);
188
189        mWifiTracker =
190                new WifiTracker(getActivity(), this, mBgThread.getLooper(), true, true, false);
191        mWifiManager = mWifiTracker.getManager();
192
193        mConnectListener = new WifiManager.ActionListener() {
194                                   @Override
195                                   public void onSuccess() {
196                                   }
197                                   @Override
198                                   public void onFailure(int reason) {
199                                       Activity activity = getActivity();
200                                       if (activity != null) {
201                                           Toast.makeText(activity,
202                                                R.string.wifi_failed_connect_message,
203                                                Toast.LENGTH_SHORT).show();
204                                       }
205                                   }
206                               };
207
208        mSaveListener = new WifiManager.ActionListener() {
209                                @Override
210                                public void onSuccess() {
211                                }
212                                @Override
213                                public void onFailure(int reason) {
214                                    Activity activity = getActivity();
215                                    if (activity != null) {
216                                        Toast.makeText(activity,
217                                            R.string.wifi_failed_save_message,
218                                            Toast.LENGTH_SHORT).show();
219                                    }
220                                }
221                            };
222
223        mForgetListener = new WifiManager.ActionListener() {
224                                   @Override
225                                   public void onSuccess() {
226                                   }
227                                   @Override
228                                   public void onFailure(int reason) {
229                                       Activity activity = getActivity();
230                                       if (activity != null) {
231                                           Toast.makeText(activity,
232                                               R.string.wifi_failed_forget_message,
233                                               Toast.LENGTH_SHORT).show();
234                                       }
235                                   }
236                               };
237
238        if (savedInstanceState != null) {
239            mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE);
240            if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
241                mAccessPointSavedState =
242                    savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
243            }
244
245            if (savedInstanceState.containsKey(SAVED_WIFI_NFC_DIALOG_STATE)) {
246                mWifiNfcDialogSavedState =
247                    savedInstanceState.getBundle(SAVED_WIFI_NFC_DIALOG_STATE);
248            }
249        }
250
251        // if we're supposed to enable/disable the Next button based on our current connection
252        // state, start it off in the right state
253        Intent intent = getActivity().getIntent();
254        mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
255
256        if (mEnableNextOnConnection) {
257            if (hasNextButton()) {
258                final ConnectivityManager connectivity = (ConnectivityManager)
259                        getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
260                if (connectivity != null) {
261                    NetworkInfo info = connectivity.getNetworkInfo(
262                            ConnectivityManager.TYPE_WIFI);
263                    changeNextButtonState(info.isConnected());
264                }
265            }
266        }
267
268        addPreferencesFromResource(R.xml.wifi_settings);
269
270        mEmptyView = initEmptyView();
271        registerForContextMenu(getListView());
272        setHasOptionsMenu(true);
273
274        if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) {
275            mOpenSsid = intent.getStringExtra(EXTRA_START_CONNECT_SSID);
276            onAccessPointsChanged();
277        }
278    }
279
280    @Override
281    public void onDestroyView() {
282        super.onDestroyView();
283
284        if (mWifiEnabler != null) {
285            mWifiEnabler.teardownSwitchBar();
286        }
287    }
288
289    @Override
290    public void onStart() {
291        super.onStart();
292
293        // On/off switch is hidden for Setup Wizard (returns null)
294        mWifiEnabler = createWifiEnabler();
295    }
296
297    /**
298     * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard)
299     */
300    /* package */ WifiEnabler createWifiEnabler() {
301        final SettingsActivity activity = (SettingsActivity) getActivity();
302        return new WifiEnabler(activity, activity.getSwitchBar());
303    }
304
305    @Override
306    public void onResume() {
307        final Activity activity = getActivity();
308        super.onResume();
309        if (mWifiEnabler != null) {
310            mWifiEnabler.resume(activity);
311        }
312
313        mWifiTracker.startTracking();
314    }
315
316    @Override
317    public void onPause() {
318        super.onPause();
319        if (mWifiEnabler != null) {
320            mWifiEnabler.pause();
321        }
322
323        mWifiTracker.stopTracking();
324    }
325
326    @Override
327    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
328        // If the user is not allowed to configure wifi, do not show the menu.
329        if (isUiRestricted()) return;
330
331        addOptionsMenuItems(menu);
332        super.onCreateOptionsMenu(menu, inflater);
333    }
334
335    /**
336     * @param menu
337     */
338    void addOptionsMenuItems(Menu menu) {
339        final boolean wifiIsEnabled = mWifiTracker.isWifiEnabled();
340        TypedArray ta = getActivity().getTheme().obtainStyledAttributes(
341                new int[] {R.attr.ic_menu_add, R.attr.ic_wps});
342        menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network)
343                .setIcon(ta.getDrawable(0))
344                .setEnabled(wifiIsEnabled)
345                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
346        if (savedNetworksExist) {
347            menu.add(Menu.NONE, MENU_ID_SAVED_NETWORK, 0, R.string.wifi_saved_access_points_label)
348                    .setIcon(ta.getDrawable(0))
349                    .setEnabled(wifiIsEnabled)
350                    .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
351        }
352        menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.menu_stats_refresh)
353               .setEnabled(wifiIsEnabled)
354               .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
355        menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
356                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
357        ta.recycle();
358    }
359
360    @Override
361    protected int getMetricsCategory() {
362        return MetricsLogger.WIFI;
363    }
364
365    @Override
366    public void onSaveInstanceState(Bundle outState) {
367        super.onSaveInstanceState(outState);
368
369        // If the dialog is showing, save its state.
370        if (mDialog != null && mDialog.isShowing()) {
371            outState.putBoolean(SAVE_DIALOG_EDIT_MODE, mDlgEdit);
372            if (mDlgAccessPoint != null) {
373                mAccessPointSavedState = new Bundle();
374                mDlgAccessPoint.saveWifiState(mAccessPointSavedState);
375                outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
376            }
377        }
378
379        if (mWifiToNfcDialog != null && mWifiToNfcDialog.isShowing()) {
380            Bundle savedState = new Bundle();
381            mWifiToNfcDialog.saveState(savedState);
382            outState.putBundle(SAVED_WIFI_NFC_DIALOG_STATE, savedState);
383        }
384    }
385
386    @Override
387    public boolean onOptionsItemSelected(MenuItem item) {
388        // If the user is not allowed to configure wifi, do not handle menu selections.
389        if (isUiRestricted()) return false;
390
391        switch (item.getItemId()) {
392            case MENU_ID_WPS_PBC:
393                showDialog(WPS_PBC_DIALOG_ID);
394                return true;
395                /*
396            case MENU_ID_P2P:
397                if (getActivity() instanceof SettingsActivity) {
398                    ((SettingsActivity) getActivity()).startPreferencePanel(
399                            WifiP2pSettings.class.getCanonicalName(),
400                            null,
401                            R.string.wifi_p2p_settings_title, null,
402                            this, 0);
403                } else {
404                    startFragment(this, WifiP2pSettings.class.getCanonicalName(),
405                            R.string.wifi_p2p_settings_title, -1, null);
406                }
407                return true;
408                */
409            case MENU_ID_WPS_PIN:
410                showDialog(WPS_PIN_DIALOG_ID);
411                return true;
412            case MENU_ID_SCAN:
413                MetricsLogger.action(getActivity(), MetricsLogger.ACTION_WIFI_FORCE_SCAN);
414                mWifiTracker.forceScan();
415                return true;
416            case MENU_ID_ADD_NETWORK:
417                if (mWifiTracker.isWifiEnabled()) {
418                    onAddNetworkPressed();
419                }
420                return true;
421            case MENU_ID_SAVED_NETWORK:
422                if (getActivity() instanceof SettingsActivity) {
423                    ((SettingsActivity) getActivity()).startPreferencePanel(
424                            SavedAccessPointsWifiSettings.class.getCanonicalName(), null,
425                            R.string.wifi_saved_access_points_titlebar, null, this, 0);
426                } else {
427                    startFragment(this, SavedAccessPointsWifiSettings.class.getCanonicalName(),
428                            R.string.wifi_saved_access_points_titlebar,
429                            -1 /* Do not request a result */, null);
430                }
431                return true;
432            case MENU_ID_ADVANCED:
433                if (getActivity() instanceof SettingsActivity) {
434                    ((SettingsActivity) getActivity()).startPreferencePanel(
435                            AdvancedWifiSettings.class.getCanonicalName(), null,
436                            R.string.wifi_advanced_titlebar, null, this, 0);
437                } else {
438                    startFragment(this, AdvancedWifiSettings.class.getCanonicalName(),
439                            R.string.wifi_advanced_titlebar, -1 /* Do not request a results */,
440                            null);
441                }
442                return true;
443        }
444        return super.onOptionsItemSelected(item);
445    }
446
447    @Override
448    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
449        if (info instanceof AdapterContextMenuInfo) {
450            Preference preference = (Preference) getListView().getItemAtPosition(
451                    ((AdapterContextMenuInfo) info).position);
452
453            if (preference instanceof AccessPointPreference) {
454                mSelectedAccessPoint = ((AccessPointPreference) preference).getAccessPoint();
455                menu.setHeaderTitle(mSelectedAccessPoint.getSsid());
456                if (mSelectedAccessPoint.isConnectable()) {
457                    menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
458                }
459
460                WifiConfiguration config = mSelectedAccessPoint.getConfig();
461                // Some configs are ineditable
462                if (isEditabilityLockedDown(getActivity(), config)) {
463                    return;
464                }
465
466                if (mSelectedAccessPoint.isSaved() || mSelectedAccessPoint.isEphemeral()) {
467                    // Allow forgetting a network if either the network is saved or ephemerally
468                    // connected. (In the latter case, "forget" blacklists the network so it won't
469                    // be used again, ephemerally).
470                    menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
471                }
472                if (mSelectedAccessPoint.isSaved()) {
473                    menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
474                    NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
475                    if (nfcAdapter != null && nfcAdapter.isEnabled() &&
476                            mSelectedAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
477                        // Only allow writing of NFC tags for password-protected networks.
478                        menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc);
479                    }
480                }
481            }
482        }
483    }
484
485    @Override
486    public boolean onContextItemSelected(MenuItem item) {
487        if (mSelectedAccessPoint == null) {
488            return super.onContextItemSelected(item);
489        }
490        switch (item.getItemId()) {
491            case MENU_ID_CONNECT: {
492                if (mSelectedAccessPoint.isSaved()) {
493                    connect(mSelectedAccessPoint.getConfig());
494                } else if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE) {
495                    /** Bypass dialog for unsecured networks */
496                    mSelectedAccessPoint.generateOpenNetworkConfig();
497                    connect(mSelectedAccessPoint.getConfig());
498                } else {
499                    showDialog(mSelectedAccessPoint, true);
500                }
501                return true;
502            }
503            case MENU_ID_FORGET: {
504                forget();
505                return true;
506            }
507            case MENU_ID_MODIFY: {
508                showDialog(mSelectedAccessPoint, true);
509                return true;
510            }
511            case MENU_ID_WRITE_NFC:
512                showDialog(WRITE_NFC_DIALOG_ID);
513                return true;
514
515        }
516        return super.onContextItemSelected(item);
517    }
518
519    @Override
520    public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
521        if (preference instanceof AccessPointPreference) {
522            mSelectedAccessPoint = ((AccessPointPreference) preference).getAccessPoint();
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                if (!savedNetworksExist) {
528                    savedNetworksExist = true;
529                    getActivity().invalidateOptionsMenu();
530                }
531                connect(mSelectedAccessPoint.getConfig());
532            } else {
533                showDialog(mSelectedAccessPoint, false);
534            }
535        } else {
536            return super.onPreferenceTreeClick(screen, preference);
537        }
538        return true;
539    }
540
541    private void showDialog(AccessPoint accessPoint, boolean edit) {
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        mDlgEdit = edit;
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, mDlgEdit,
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
632        if (isUiRestricted()) {
633            addMessagePreference(R.string.wifi_empty_list_user_restricted);
634            return;
635        }
636        final int wifiState = mWifiManager.getWifiState();
637
638        switch (wifiState) {
639            case WifiManager.WIFI_STATE_ENABLED:
640                // AccessPoints are automatically sorted with TreeSet.
641                final Collection<AccessPoint> accessPoints =
642                        mWifiTracker.getAccessPoints();
643                getPreferenceScreen().removeAll();
644
645                boolean hasAvailableAccessPoints = false;
646                for (AccessPoint accessPoint : accessPoints) {
647                    // Ignore access points that are out of range.
648                    if (accessPoint.getLevel() != -1) {
649                        hasAvailableAccessPoints = true;
650                        AccessPointPreference preference = new AccessPointPreference(accessPoint,
651                                getActivity(), false);
652
653                        if (mOpenSsid != null && mOpenSsid.equals(accessPoint.getSsid())
654                                && !accessPoint.isSaved()
655                                && accessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
656                            onPreferenceTreeClick(getPreferenceScreen(), preference);
657                            mOpenSsid = null;
658                        }
659                        getPreferenceScreen().addPreference(preference);
660                        accessPoint.setListener(this);
661                    }
662                }
663                if (!hasAvailableAccessPoints) {
664                    setProgressBarVisible(true);
665                    addMessagePreference(R.string.wifi_empty_list_wifi_on);
666                } else {
667                    setProgressBarVisible(false);
668                }
669                break;
670
671            case WifiManager.WIFI_STATE_ENABLING:
672                getPreferenceScreen().removeAll();
673                setProgressBarVisible(true);
674                break;
675
676            case WifiManager.WIFI_STATE_DISABLING:
677                addMessagePreference(R.string.wifi_stopping);
678                setProgressBarVisible(true);
679                break;
680
681            case WifiManager.WIFI_STATE_DISABLED:
682                setOffMessage();
683                setProgressBarVisible(false);
684                break;
685        }
686        // Update "Saved Networks" menu option.
687        if (savedNetworksExist != mWifiTracker.doSavedNetworksExist()) {
688            savedNetworksExist = !savedNetworksExist;
689            getActivity().invalidateOptionsMenu();
690        }
691    }
692
693    protected TextView initEmptyView() {
694        TextView emptyView = (TextView) getActivity().findViewById(android.R.id.empty);
695        emptyView.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
696        getListView().setEmptyView(emptyView);
697        return emptyView;
698    }
699
700    private void setOffMessage() {
701        if (mEmptyView == null) {
702            return;
703        }
704
705        final CharSequence briefText = getText(R.string.wifi_empty_list_wifi_off);
706        if (isUiRestricted()) {
707            // Show only the brief text if the user is not allowed to configure scanning settings.
708            mEmptyView.setText(briefText, BufferType.SPANNABLE);
709        } else {
710            // Append the description of scanning settings with link.
711            final StringBuilder contentBuilder = new StringBuilder();
712            contentBuilder.append(briefText);
713            contentBuilder.append("\n\n");
714            contentBuilder.append(getText(R.string.wifi_scan_notify_text));
715            LinkifyUtils.linkify(mEmptyView, contentBuilder, new LinkifyUtils.OnClickListener() {
716                @Override
717                public void onClick() {
718                    final SettingsActivity activity =
719                            (SettingsActivity) WifiSettings.this.getActivity();
720                    activity.startPreferencePanel(ScanningSettings.class.getName(), null,
721                            R.string.location_scanning_screen_title, null, null, 0);
722                }
723            });
724        }
725        // Embolden and enlarge the brief description anyway.
726        Spannable boldSpan = (Spannable) mEmptyView.getText();
727        boldSpan.setSpan(
728                new TextAppearanceSpan(getActivity(), android.R.style.TextAppearance_Medium), 0,
729                briefText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
730        getPreferenceScreen().removeAll();
731    }
732
733    private void addMessagePreference(int messageId) {
734        if (mEmptyView != null) mEmptyView.setText(messageId);
735        getPreferenceScreen().removeAll();
736    }
737
738    protected void setProgressBarVisible(boolean visible) {
739        if (mProgressHeader != null) {
740            mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE);
741        }
742    }
743
744    @Override
745    public void onWifiStateChanged(int state) {
746        Activity activity = getActivity();
747        if (activity != null) {
748            activity.invalidateOptionsMenu();
749        }
750
751        switch (state) {
752            case WifiManager.WIFI_STATE_ENABLING:
753                addMessagePreference(R.string.wifi_starting);
754                setProgressBarVisible(true);
755                break;
756
757            case WifiManager.WIFI_STATE_DISABLED:
758                setOffMessage();
759                setProgressBarVisible(false);
760                break;
761        }
762    }
763
764    @Override
765    public void onConnectedChanged() {
766        changeNextButtonState(mWifiTracker.isConnected());
767    }
768
769    /**
770     * Renames/replaces "Next" button when appropriate. "Next" button usually exists in
771     * Wifi setup screens, not in usual wifi settings screen.
772     *
773     * @param enabled true when the device is connected to a wifi network.
774     */
775    private void changeNextButtonState(boolean enabled) {
776        if (mEnableNextOnConnection && hasNextButton()) {
777            getNextButton().setEnabled(enabled);
778        }
779    }
780
781    @Override
782    public void onClick(DialogInterface dialogInterface, int button) {
783        if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) {
784            forget();
785        } else if (button == WifiDialog.BUTTON_SUBMIT) {
786            if (mDialog != null) {
787                submit(mDialog.getController());
788            }
789        }
790    }
791
792    /* package */ void submit(WifiConfigController configController) {
793
794        final WifiConfiguration config = configController.getConfig();
795
796        if (config == null) {
797            if (mSelectedAccessPoint != null
798                    && mSelectedAccessPoint.isSaved()) {
799                connect(mSelectedAccessPoint.getConfig());
800            }
801        } else if (config.networkId != INVALID_NETWORK_ID) {
802            if (mSelectedAccessPoint != null) {
803                mWifiManager.save(config, mSaveListener);
804            }
805        } else {
806            if (configController.isEdit()) {
807                mWifiManager.save(config, mSaveListener);
808            } else {
809                connect(config);
810            }
811        }
812
813        mWifiTracker.resumeScanning();
814    }
815
816    /* package */ void forget() {
817        MetricsLogger.action(getActivity(), MetricsLogger.ACTION_WIFI_FORGET);
818        if (!mSelectedAccessPoint.isSaved()) {
819            if (mSelectedAccessPoint.getNetworkInfo() != null &&
820                    mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED) {
821                // Network is active but has no network ID - must be ephemeral.
822                mWifiManager.disableEphemeralNetwork(
823                        AccessPoint.convertToQuotedString(mSelectedAccessPoint.getSsid()));
824            } else {
825                // Should not happen, but a monkey seems to trigger it
826                Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig());
827                return;
828            }
829        } else {
830            mWifiManager.forget(mSelectedAccessPoint.getConfig().networkId, mForgetListener);
831        }
832
833        mWifiTracker.resumeScanning();
834
835        // We need to rename/replace "Next" button in wifi setup context.
836        changeNextButtonState(false);
837    }
838
839    protected void connect(final WifiConfiguration config) {
840        MetricsLogger.action(getActivity(), MetricsLogger.ACTION_WIFI_CONNECT);
841        mWifiManager.connect(config, mConnectListener);
842    }
843
844    protected void connect(final int networkId) {
845        MetricsLogger.action(getActivity(), MetricsLogger.ACTION_WIFI_CONNECT);
846        mWifiManager.connect(networkId, mConnectListener);
847    }
848
849    /**
850     * Refreshes acccess points and ask Wifi module to scan networks again.
851     */
852    /* package */ void refreshAccessPoints() {
853        mWifiTracker.resumeScanning();
854
855        getPreferenceScreen().removeAll();
856    }
857
858    /**
859     * Called when "add network" button is pressed.
860     */
861    /* package */ void onAddNetworkPressed() {
862        MetricsLogger.action(getActivity(), MetricsLogger.ACTION_WIFI_ADD_NETWORK);
863        // No exact access point is selected.
864        mSelectedAccessPoint = null;
865        showDialog(null, true);
866    }
867
868    /* package */ int getAccessPointsCount() {
869        final boolean wifiIsEnabled = mWifiTracker.isWifiEnabled();
870        if (wifiIsEnabled) {
871            return getPreferenceScreen().getPreferenceCount();
872        } else {
873            return 0;
874        }
875    }
876
877    /**
878     * Requests wifi module to pause wifi scan. May be ignored when the module is disabled.
879     */
880    /* package */ void pauseWifiScan() {
881        mWifiTracker.pauseScanning();
882    }
883
884    /**
885     * Requests wifi module to resume wifi scan. May be ignored when the module is disabled.
886     */
887    /* package */ void resumeWifiScan() {
888        mWifiTracker.resumeScanning();
889    }
890
891    @Override
892    protected int getHelpResource() {
893        return R.string.help_url_wifi;
894    }
895
896    @Override
897    public void onAccessPointChanged(AccessPoint accessPoint) {
898        ((AccessPointPreference) accessPoint.getTag()).refresh();
899    }
900
901    @Override
902    public void onLevelChanged(AccessPoint accessPoint) {
903        ((AccessPointPreference) accessPoint.getTag()).onLevelChanged();
904    }
905
906    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
907        new BaseSearchIndexProvider() {
908            @Override
909            public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
910                final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
911                final Resources res = context.getResources();
912
913                // Add fragment title
914                SearchIndexableRaw data = new SearchIndexableRaw(context);
915                data.title = res.getString(R.string.wifi_settings);
916                data.screenTitle = res.getString(R.string.wifi_settings);
917                data.keywords = res.getString(R.string.keywords_wifi);
918                result.add(data);
919
920                // Add saved Wi-Fi access points
921                final Collection<AccessPoint> accessPoints =
922                        WifiTracker.getCurrentAccessPoints(context, true, false, false);
923                for (AccessPoint accessPoint : accessPoints) {
924                    data = new SearchIndexableRaw(context);
925                    data.title = accessPoint.getSsid();
926                    data.screenTitle = res.getString(R.string.wifi_settings);
927                    data.enabled = enabled;
928                    result.add(data);
929                }
930
931                return result;
932            }
933        };
934
935    /**
936     * Returns true if the config is not editable/removable except by its creating Device Owner.
937     * @param config The WiFi config.
938     * @return true if the config is not editable/removable except by its creating Device Owner.
939     */
940    static boolean isEditabilityLockedDown(Context context, WifiConfiguration config) {
941        if (config == null) {
942            return false;
943        }
944        final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
945                Context.DEVICE_POLICY_SERVICE);
946        final String deviceOwnerPackageName = dpm.getDeviceOwner();
947        if (deviceOwnerPackageName == null) {
948            return false;
949        }
950        UserManager um = UserManager.get(context);
951        if (um.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI)) {
952            return false;
953        }
954        final PackageManager pm = context.getPackageManager();
955        try {
956            final int deviceOwnerUid = pm.getPackageUid(deviceOwnerPackageName,
957                    UserHandle.getUserId(config.creatorUid));
958            return deviceOwnerUid == config.creatorUid;
959        } catch (NameNotFoundException e) {
960            return false;
961        }
962    }
963}
964