WifiSettings.java revision 2e75203a6ecb81725b68bced6c5f8d5be37365f1
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.ActivityManager;
24import android.app.Dialog;
25import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.DialogInterface;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.res.Resources;
31import android.content.res.TypedArray;
32import android.location.LocationManager;
33import android.net.ConnectivityManager;
34import android.net.NetworkInfo;
35import android.net.NetworkInfo.DetailedState;
36import android.net.NetworkInfo.State;
37import android.net.wifi.ScanResult;
38import android.net.wifi.WifiConfiguration;
39import android.net.wifi.WifiInfo;
40import android.net.wifi.WifiManager;
41import android.net.wifi.WpsInfo;
42import android.nfc.NfcAdapter;
43import android.os.Bundle;
44import android.os.Handler;
45import android.os.Message;
46import android.os.UserHandle;
47import android.preference.Preference;
48import android.preference.PreferenceScreen;
49import android.util.Log;
50import android.view.ContextMenu;
51import android.view.ContextMenu.ContextMenuInfo;
52import android.view.Menu;
53import android.view.MenuInflater;
54import android.view.MenuItem;
55import android.view.View;
56import android.widget.AdapterView.AdapterContextMenuInfo;
57import android.widget.TextView;
58import android.widget.Toast;
59
60import com.android.settings.R;
61import com.android.settings.RestrictedSettingsFragment;
62import com.android.settings.SettingsActivity;
63import com.android.settings.search.BaseSearchIndexProvider;
64import com.android.settings.search.Indexable;
65import com.android.settings.search.SearchIndexableRaw;
66
67import java.util.ArrayList;
68import java.util.Collection;
69import java.util.Collections;
70import java.util.HashMap;
71import java.util.List;
72import java.util.concurrent.atomic.AtomicBoolean;
73
74/**
75 * Two types of UI are provided here.
76 *
77 * The first is for "usual Settings", appearing as any other Setup fragment.
78 *
79 * The second is for Setup Wizard, with a simplified interface that hides the action bar
80 * and menus.
81 */
82public class WifiSettings extends RestrictedSettingsFragment
83        implements DialogInterface.OnClickListener, Indexable  {
84
85    private static final String TAG = "WifiSettings";
86
87    /* package */ static final int MENU_ID_WPS_PBC = Menu.FIRST;
88    private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1;
89    private static final int MENU_ID_SAVED_NETWORK = Menu.FIRST + 2;
90    /* package */ static final int MENU_ID_ADD_NETWORK = Menu.FIRST + 3;
91    private static final int MENU_ID_ADVANCED = Menu.FIRST + 4;
92    private static final int MENU_ID_SCAN = Menu.FIRST + 5;
93    private static final int MENU_ID_CONNECT = Menu.FIRST + 6;
94    private static final int MENU_ID_FORGET = Menu.FIRST + 7;
95    private static final int MENU_ID_MODIFY = Menu.FIRST + 8;
96    private static final int MENU_ID_WRITE_NFC = Menu.FIRST + 9;
97
98    public static final int WIFI_DIALOG_ID = 1;
99    /* package */ static final int WPS_PBC_DIALOG_ID = 2;
100    private static final int WPS_PIN_DIALOG_ID = 3;
101    private static final int WRITE_NFC_DIALOG_ID = 6;
102
103    // Combo scans can take 5-6s to complete - set to 10s.
104    private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
105
106    // Instance state keys
107    private static final String SAVE_DIALOG_EDIT_MODE = "edit_mode";
108    private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
109
110    private static boolean savedNetworksExist;
111
112    private final IntentFilter mFilter;
113    private final BroadcastReceiver mReceiver;
114    private final Scanner mScanner;
115
116    /* package */ WifiManager mWifiManager;
117    private WifiManager.ActionListener mConnectListener;
118    private WifiManager.ActionListener mSaveListener;
119    private WifiManager.ActionListener mForgetListener;
120
121    private WifiEnabler mWifiEnabler;
122    // An access point being editted is stored here.
123    private AccessPoint mSelectedAccessPoint;
124
125    private NetworkInfo mLastNetworkInfo;
126    private WifiInfo mLastInfo;
127
128    private final AtomicBoolean mConnected = new AtomicBoolean(false);
129
130    private WifiDialog mDialog;
131    private WriteWifiConfigToNfcDialog mWifiToNfcDialog;
132
133    private TextView mEmptyView;
134
135    // this boolean extra specifies whether to disable the Next button when not connected. Used by
136    // account creation outside of setup wizard.
137    private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
138    // This string extra specifies a network to open the connect dialog on, so the user can enter
139    // network credentials.  This is used by quick settings for secured networks.
140    private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
141
142    // should Next button only be enabled when we have a connection?
143    private boolean mEnableNextOnConnection;
144
145    // Save the dialog details
146    private boolean mDlgEdit;
147    private AccessPoint mDlgAccessPoint;
148    private Bundle mAccessPointSavedState;
149
150    /** verbose logging flag. this flag is set thru developer debugging options
151     * and used so as to assist with in-the-field WiFi connectivity debugging  */
152    public static int mVerboseLogging = 0;
153
154    /* End of "used in Wifi Setup context" */
155
156    /** A restricted multimap for use in constructAccessPoints */
157    private static class Multimap<K,V> {
158        private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
159        /** retrieve a non-null list of values with key K */
160        List<V> getAll(K key) {
161            List<V> values = store.get(key);
162            return values != null ? values : Collections.<V>emptyList();
163        }
164
165        void put(K key, V val) {
166            List<V> curVals = store.get(key);
167            if (curVals == null) {
168                curVals = new ArrayList<V>(3);
169                store.put(key, curVals);
170            }
171            curVals.add(val);
172        }
173    }
174
175    private static class Scanner extends Handler {
176        private int mRetry = 0;
177        private WifiSettings mWifiSettings = null;
178
179        Scanner(WifiSettings wifiSettings) {
180            mWifiSettings = wifiSettings;
181        }
182
183        void resume() {
184            if (!hasMessages(0)) {
185                sendEmptyMessage(0);
186            }
187        }
188
189        void forceScan() {
190            removeMessages(0);
191            sendEmptyMessage(0);
192        }
193
194        void pause() {
195            mRetry = 0;
196            removeMessages(0);
197        }
198
199        @Override
200        public void handleMessage(Message message) {
201            if (mWifiSettings.mWifiManager.startScan()) {
202                mRetry = 0;
203            } else if (++mRetry >= 3) {
204                mRetry = 0;
205                Activity activity = mWifiSettings.getActivity();
206                if (activity != null) {
207                    Toast.makeText(activity, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
208                }
209                return;
210            }
211            sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
212        }
213    }
214
215    public WifiSettings() {
216        super(DISALLOW_CONFIG_WIFI);
217        mFilter = new IntentFilter();
218        mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
219        mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
220        mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
221        mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
222        mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
223        mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
224        mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
225        mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
226
227        mReceiver = new BroadcastReceiver() {
228            @Override
229            public void onReceive(Context context, Intent intent) {
230                handleEvent(intent);
231            }
232        };
233
234        mScanner = new Scanner(this);
235    }
236
237    @Override
238    public void onActivityCreated(Bundle savedInstanceState) {
239        super.onActivityCreated(savedInstanceState);
240
241        mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
242
243        mConnectListener = new WifiManager.ActionListener() {
244                                   @Override
245                                   public void onSuccess() {
246                                   }
247                                   @Override
248                                   public void onFailure(int reason) {
249                                       Activity activity = getActivity();
250                                       if (activity != null) {
251                                           Toast.makeText(activity,
252                                                R.string.wifi_failed_connect_message,
253                                                Toast.LENGTH_SHORT).show();
254                                       }
255                                   }
256                               };
257
258        mSaveListener = new WifiManager.ActionListener() {
259                                @Override
260                                public void onSuccess() {
261                                }
262                                @Override
263                                public void onFailure(int reason) {
264                                    Activity activity = getActivity();
265                                    if (activity != null) {
266                                        Toast.makeText(activity,
267                                            R.string.wifi_failed_save_message,
268                                            Toast.LENGTH_SHORT).show();
269                                    }
270                                }
271                            };
272
273        mForgetListener = new WifiManager.ActionListener() {
274                                   @Override
275                                   public void onSuccess() {
276                                   }
277                                   @Override
278                                   public void onFailure(int reason) {
279                                       Activity activity = getActivity();
280                                       if (activity != null) {
281                                           Toast.makeText(activity,
282                                               R.string.wifi_failed_forget_message,
283                                               Toast.LENGTH_SHORT).show();
284                                       }
285                                   }
286                               };
287
288        if (savedInstanceState != null) {
289            mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE);
290            if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
291                mAccessPointSavedState =
292                    savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
293            }
294        }
295
296        // if we're supposed to enable/disable the Next button based on our current connection
297        // state, start it off in the right state
298        Intent intent = getActivity().getIntent();
299        mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
300
301        if (mEnableNextOnConnection) {
302            if (hasNextButton()) {
303                final ConnectivityManager connectivity = (ConnectivityManager)
304                        getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
305                if (connectivity != null) {
306                    NetworkInfo info = connectivity.getNetworkInfo(
307                            ConnectivityManager.TYPE_WIFI);
308                    changeNextButtonState(info.isConnected());
309                }
310            }
311        }
312
313        addPreferencesFromResource(R.xml.wifi_settings);
314
315        mEmptyView = initEmptyView();
316        registerForContextMenu(getListView());
317        setHasOptionsMenu(true);
318
319        if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) {
320            String ssid = intent.getStringExtra(EXTRA_START_CONNECT_SSID);
321            updateAccessPoints();
322            PreferenceScreen preferenceScreen = getPreferenceScreen();
323            for (int i = 0; i < preferenceScreen.getPreferenceCount(); i++) {
324                Preference preference = preferenceScreen.getPreference(i);
325                if (preference instanceof AccessPoint) {
326                    AccessPoint accessPoint = (AccessPoint) preference;
327                    if (ssid.equals(accessPoint.ssid) && accessPoint.networkId == -1
328                            && accessPoint.security != AccessPoint.SECURITY_NONE) {
329                        onPreferenceTreeClick(preferenceScreen, preference);
330                        break;
331                    }
332                }
333            }
334        }
335    }
336
337    @Override
338    public void onDestroyView() {
339        super.onDestroyView();
340
341        if (mWifiEnabler != null) {
342            mWifiEnabler.teardownSwitchBar();
343        }
344    }
345
346    @Override
347    public void onStart() {
348        super.onStart();
349
350        // On/off switch is hidden for Setup Wizard (returns null)
351        mWifiEnabler = createWifiEnabler();
352    }
353
354    /**
355     * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard)
356     */
357    /* package */ WifiEnabler createWifiEnabler() {
358        final SettingsActivity activity = (SettingsActivity) getActivity();
359        return new WifiEnabler(activity, activity.getSwitchBar());
360    }
361
362    @Override
363    public void onResume() {
364        final Activity activity = getActivity();
365        super.onResume();
366        if (mWifiEnabler != null) {
367            mWifiEnabler.resume(activity);
368        }
369
370        activity.registerReceiver(mReceiver, mFilter);
371        updateAccessPoints();
372    }
373
374    @Override
375    public void onPause() {
376        super.onPause();
377        if (mWifiEnabler != null) {
378            mWifiEnabler.pause();
379        }
380
381        getActivity().unregisterReceiver(mReceiver);
382        mScanner.pause();
383    }
384
385    @Override
386    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
387        // If the user is not allowed to configure wifi, do not show the menu.
388        if (isUiRestricted()) return;
389
390        addOptionsMenuItems(menu);
391        super.onCreateOptionsMenu(menu, inflater);
392    }
393
394    /**
395     * @param menu
396     */
397    void addOptionsMenuItems(Menu menu) {
398        final boolean wifiIsEnabled = mWifiManager.isWifiEnabled();
399        TypedArray ta = getActivity().getTheme().obtainStyledAttributes(
400                new int[] {R.attr.ic_menu_add, R.attr.ic_wps});
401        menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network)
402                .setIcon(ta.getDrawable(0))
403                .setEnabled(wifiIsEnabled)
404                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
405        if (savedNetworksExist) {
406            menu.add(Menu.NONE, MENU_ID_SAVED_NETWORK, 0, R.string.wifi_saved_access_points_label)
407                    .setIcon(ta.getDrawable(0))
408                    .setEnabled(wifiIsEnabled)
409                    .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
410        }
411        menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.menu_stats_refresh)
412               .setEnabled(wifiIsEnabled)
413               .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
414        menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
415                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
416        ta.recycle();
417    }
418
419    @Override
420    public void onSaveInstanceState(Bundle outState) {
421        super.onSaveInstanceState(outState);
422
423        // If the dialog is showing, save its state.
424        if (mDialog != null && mDialog.isShowing()) {
425            outState.putBoolean(SAVE_DIALOG_EDIT_MODE, mDlgEdit);
426            if (mDlgAccessPoint != null) {
427                mAccessPointSavedState = new Bundle();
428                mDlgAccessPoint.saveWifiState(mAccessPointSavedState);
429                outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
430            }
431        }
432    }
433
434    @Override
435    public boolean onOptionsItemSelected(MenuItem item) {
436        // If the user is not allowed to configure wifi, do not handle menu selections.
437        if (isUiRestricted()) return false;
438
439        switch (item.getItemId()) {
440            case MENU_ID_WPS_PBC:
441                showDialog(WPS_PBC_DIALOG_ID);
442                return true;
443                /*
444            case MENU_ID_P2P:
445                if (getActivity() instanceof SettingsActivity) {
446                    ((SettingsActivity) getActivity()).startPreferencePanel(
447                            WifiP2pSettings.class.getCanonicalName(),
448                            null,
449                            R.string.wifi_p2p_settings_title, null,
450                            this, 0);
451                } else {
452                    startFragment(this, WifiP2pSettings.class.getCanonicalName(),
453                            R.string.wifi_p2p_settings_title, -1, null);
454                }
455                return true;
456                */
457            case MENU_ID_WPS_PIN:
458                showDialog(WPS_PIN_DIALOG_ID);
459                return true;
460            case MENU_ID_SCAN:
461                if (mWifiManager.isWifiEnabled()) {
462                    mScanner.forceScan();
463                }
464                return true;
465            case MENU_ID_ADD_NETWORK:
466                if (mWifiManager.isWifiEnabled()) {
467                    onAddNetworkPressed();
468                }
469                return true;
470            case MENU_ID_SAVED_NETWORK:
471                if (getActivity() instanceof SettingsActivity) {
472                    ((SettingsActivity) getActivity()).startPreferencePanel(
473                            SavedAccessPointsWifiSettings.class.getCanonicalName(), null,
474                            R.string.wifi_saved_access_points_titlebar, null, this, 0);
475                } else {
476                    startFragment(this, SavedAccessPointsWifiSettings.class.getCanonicalName(),
477                            R.string.wifi_saved_access_points_titlebar,
478                            -1 /* Do not request a result */, null);
479                }
480                return true;
481            case MENU_ID_ADVANCED:
482                if (getActivity() instanceof SettingsActivity) {
483                    ((SettingsActivity) getActivity()).startPreferencePanel(
484                            AdvancedWifiSettings.class.getCanonicalName(), null,
485                            R.string.wifi_advanced_titlebar, null, this, 0);
486                } else {
487                    startFragment(this, AdvancedWifiSettings.class.getCanonicalName(),
488                            R.string.wifi_advanced_titlebar, -1 /* Do not request a results */,
489                            null);
490                }
491                return true;
492        }
493        return super.onOptionsItemSelected(item);
494    }
495
496    @Override
497    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
498        if (info instanceof AdapterContextMenuInfo) {
499            Preference preference = (Preference) getListView().getItemAtPosition(
500                    ((AdapterContextMenuInfo) info).position);
501
502            if (preference instanceof AccessPoint) {
503                mSelectedAccessPoint = (AccessPoint) preference;
504                menu.setHeaderTitle(mSelectedAccessPoint.ssid);
505                if (mSelectedAccessPoint.getLevel() != -1) {
506                    if (mSelectedAccessPoint.getState() == null) {
507                        menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
508                    }
509                }
510
511                if (ActivityManager.getCurrentUser() == UserHandle.USER_OWNER &&
512                        (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID ||
513                        (mSelectedAccessPoint.getNetworkInfo() != null &&
514                        mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED))) {
515                    // Allow forgetting a network if the current user is the owner and either the
516                    // network is saved or ephemerally connected. (In the latter case, "forget"
517                    // blacklists the network so it won't be used again, ephemerally).
518                    menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
519                }
520                if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
521                    menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
522                    NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
523                    if (nfcAdapter != null && nfcAdapter.isEnabled() &&
524                            mSelectedAccessPoint.security != AccessPoint.SECURITY_NONE) {
525                        // Only allow writing of NFC tags for password-protected networks.
526                        menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc);
527                    }
528                }
529            }
530        }
531    }
532
533    @Override
534    public boolean onContextItemSelected(MenuItem item) {
535        if (mSelectedAccessPoint == null) {
536            return super.onContextItemSelected(item);
537        }
538        switch (item.getItemId()) {
539            case MENU_ID_CONNECT: {
540                if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
541                    connect(mSelectedAccessPoint.networkId);
542                } else if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE) {
543                    /** Bypass dialog for unsecured networks */
544                    mSelectedAccessPoint.generateOpenNetworkConfig();
545                    connect(mSelectedAccessPoint.getConfig());
546                } else {
547                    showDialog(mSelectedAccessPoint, true);
548                }
549                return true;
550            }
551            case MENU_ID_FORGET: {
552                forget();
553                return true;
554            }
555            case MENU_ID_MODIFY: {
556                showDialog(mSelectedAccessPoint, true);
557                return true;
558            }
559            case MENU_ID_WRITE_NFC:
560                showDialog(WRITE_NFC_DIALOG_ID);
561                return true;
562
563        }
564        return super.onContextItemSelected(item);
565    }
566
567    @Override
568    public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
569        if (preference instanceof AccessPoint) {
570            mSelectedAccessPoint = (AccessPoint) preference;
571            /** Bypass dialog for unsecured, unsaved, and inactive networks */
572            if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE &&
573                    mSelectedAccessPoint.networkId == INVALID_NETWORK_ID &&
574                    !mSelectedAccessPoint.isActive()) {
575                mSelectedAccessPoint.generateOpenNetworkConfig();
576                if (!savedNetworksExist) {
577                    savedNetworksExist = true;
578                    getActivity().invalidateOptionsMenu();
579                }
580                connect(mSelectedAccessPoint.getConfig());
581            } else {
582                showDialog(mSelectedAccessPoint, false);
583            }
584        } else {
585            return super.onPreferenceTreeClick(screen, preference);
586        }
587        return true;
588    }
589
590    private void showDialog(AccessPoint accessPoint, boolean edit) {
591        if (mDialog != null) {
592            removeDialog(WIFI_DIALOG_ID);
593            mDialog = null;
594        }
595
596        // Save the access point and edit mode
597        mDlgAccessPoint = accessPoint;
598        mDlgEdit = edit;
599
600        showDialog(WIFI_DIALOG_ID);
601    }
602
603    @Override
604    public Dialog onCreateDialog(int dialogId) {
605        switch (dialogId) {
606            case WIFI_DIALOG_ID:
607                AccessPoint ap = mDlgAccessPoint; // For manual launch
608                if (ap == null) { // For re-launch from saved state
609                    if (mAccessPointSavedState != null) {
610                        ap = new AccessPoint(getActivity(), mAccessPointSavedState);
611                        // For repeated orientation changes
612                        mDlgAccessPoint = ap;
613                        // Reset the saved access point data
614                        mAccessPointSavedState = null;
615                    }
616                }
617                // If it's null, fine, it's for Add Network
618                mSelectedAccessPoint = ap;
619                mDialog = new WifiDialog(getActivity(), this, ap, mDlgEdit);
620                return mDialog;
621            case WPS_PBC_DIALOG_ID:
622                return new WpsDialog(getActivity(), WpsInfo.PBC);
623            case WPS_PIN_DIALOG_ID:
624                return new WpsDialog(getActivity(), WpsInfo.DISPLAY);
625            case WRITE_NFC_DIALOG_ID:
626                if (mSelectedAccessPoint != null) {
627                    mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
628                            getActivity(), mSelectedAccessPoint, mWifiManager);
629                    return mWifiToNfcDialog;
630                }
631
632        }
633        return super.onCreateDialog(dialogId);
634    }
635
636    /**
637     * Shows the latest access points available with supplemental information like
638     * the strength of network and the security for it.
639     */
640    private void updateAccessPoints() {
641        // Safeguard from some delayed event handling
642        if (getActivity() == null) return;
643
644        if (isUiRestricted()) {
645            addMessagePreference(R.string.wifi_empty_list_user_restricted);
646            return;
647        }
648        final int wifiState = mWifiManager.getWifiState();
649
650        //when we update the screen, check if verbose logging has been turned on or off
651        mVerboseLogging = mWifiManager.getVerboseLoggingLevel();
652
653        switch (wifiState) {
654            case WifiManager.WIFI_STATE_ENABLED:
655                // AccessPoints are automatically sorted with TreeSet.
656                final Collection<AccessPoint> accessPoints =
657                        constructAccessPoints(getActivity(), mWifiManager, mLastInfo,
658                                mLastNetworkInfo);
659                getPreferenceScreen().removeAll();
660                if (accessPoints.size() == 0) {
661                    addMessagePreference(R.string.wifi_empty_list_wifi_on);
662                }
663
664                for (AccessPoint accessPoint : accessPoints) {
665                    // Ignore access points that are out of range.
666                    if (accessPoint.getLevel() != -1) {
667                        getPreferenceScreen().addPreference(accessPoint);
668                    }
669                }
670                break;
671
672            case WifiManager.WIFI_STATE_ENABLING:
673                getPreferenceScreen().removeAll();
674                break;
675
676            case WifiManager.WIFI_STATE_DISABLING:
677                addMessagePreference(R.string.wifi_stopping);
678                break;
679
680            case WifiManager.WIFI_STATE_DISABLED:
681                setOffMessage();
682                break;
683        }
684    }
685
686    protected TextView initEmptyView() {
687        TextView emptyView = (TextView) getActivity().findViewById(android.R.id.empty);
688        getListView().setEmptyView(emptyView);
689        return emptyView;
690    }
691
692    private void setOffMessage() {
693        if (mEmptyView != null) {
694            mEmptyView.setText(R.string.wifi_empty_list_wifi_off);
695            if (android.provider.Settings.Global.getInt(getActivity().getContentResolver(),
696                    android.provider.Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1) {
697                mEmptyView.append("\n\n");
698                int resId;
699                if (android.provider.Settings.Secure.isLocationProviderEnabled(
700                        getActivity().getContentResolver(), LocationManager.NETWORK_PROVIDER)) {
701                    resId = R.string.wifi_scan_notify_text_location_on;
702                } else {
703                    resId = R.string.wifi_scan_notify_text_location_off;
704                }
705                CharSequence charSeq = getText(resId);
706                mEmptyView.append(charSeq);
707            }
708        }
709        getPreferenceScreen().removeAll();
710    }
711
712    private void addMessagePreference(int messageId) {
713        if (mEmptyView != null) mEmptyView.setText(messageId);
714        getPreferenceScreen().removeAll();
715    }
716
717    /** Returns sorted list of access points */
718    private static List<AccessPoint> constructAccessPoints(Context context,
719            WifiManager wifiManager, WifiInfo lastInfo, NetworkInfo lastNetworkInfo) {
720        ArrayList<AccessPoint> accessPoints = new ArrayList<AccessPoint>();
721        /** Lookup table to more quickly update AccessPoints by only considering objects with the
722         * correct SSID.  Maps SSID -> List of AccessPoints with the given SSID.  */
723        Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
724
725        final List<WifiConfiguration> configs = wifiManager.getConfiguredNetworks();
726        if (configs != null) {
727            // Update "Saved Networks" menu option.
728            if (savedNetworksExist != (configs.size() > 0)) {
729                savedNetworksExist = !savedNetworksExist;
730                if (context instanceof Activity) {
731                    ((Activity) context).invalidateOptionsMenu();
732                }
733            }
734            for (WifiConfiguration config : configs) {
735                if (config.selfAdded && config.numAssociation == 0) {
736                    continue;
737                }
738                AccessPoint accessPoint = new AccessPoint(context, config);
739                if (lastInfo != null && lastNetworkInfo != null) {
740                    accessPoint.update(lastInfo, lastNetworkInfo);
741                }
742                accessPoints.add(accessPoint);
743                apMap.put(accessPoint.ssid, accessPoint);
744            }
745        }
746
747        final List<ScanResult> results = wifiManager.getScanResults();
748        if (results != null) {
749            for (ScanResult result : results) {
750                // Ignore hidden and ad-hoc networks.
751                if (result.SSID == null || result.SSID.length() == 0 ||
752                        result.capabilities.contains("[IBSS]")) {
753                    continue;
754                }
755
756                boolean found = false;
757                for (AccessPoint accessPoint : apMap.getAll(result.SSID)) {
758                    if (accessPoint.update(result))
759                        found = true;
760                }
761                if (!found) {
762                    AccessPoint accessPoint = new AccessPoint(context, result);
763                    if (lastInfo != null && lastNetworkInfo != null) {
764                        accessPoint.update(lastInfo, lastNetworkInfo);
765                    }
766                    accessPoints.add(accessPoint);
767                    apMap.put(accessPoint.ssid, accessPoint);
768                }
769            }
770        }
771
772        // Pre-sort accessPoints to speed preference insertion
773        Collections.sort(accessPoints);
774        return accessPoints;
775    }
776
777    private void handleEvent(Intent intent) {
778        String action = intent.getAction();
779        if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
780            updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
781                    WifiManager.WIFI_STATE_UNKNOWN));
782        } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||
783                WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
784                WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
785                updateAccessPoints();
786        } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
787            NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
788                    WifiManager.EXTRA_NETWORK_INFO);
789            mConnected.set(info.isConnected());
790            changeNextButtonState(info.isConnected());
791            updateAccessPoints();
792            updateNetworkInfo(info);
793        } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
794            updateNetworkInfo(null);
795        }
796    }
797
798    private void updateNetworkInfo(NetworkInfo networkInfo) {
799        /* sticky broadcasts can call this when wifi is disabled */
800        if (!mWifiManager.isWifiEnabled()) {
801            mScanner.pause();
802            return;
803        }
804
805        if (networkInfo != null &&
806                networkInfo.getDetailedState() == DetailedState.OBTAINING_IPADDR) {
807            mScanner.pause();
808        } else {
809            mScanner.resume();
810        }
811
812        mLastInfo = mWifiManager.getConnectionInfo();
813        if (networkInfo != null) {
814            mLastNetworkInfo = networkInfo;
815        }
816
817        for (int i = getPreferenceScreen().getPreferenceCount() - 1; i >= 0; --i) {
818            // Maybe there's a WifiConfigPreference
819            Preference preference = getPreferenceScreen().getPreference(i);
820            if (preference instanceof AccessPoint) {
821                final AccessPoint accessPoint = (AccessPoint) preference;
822                accessPoint.update(mLastInfo, mLastNetworkInfo);
823            }
824        }
825    }
826
827    private void updateWifiState(int state) {
828        Activity activity = getActivity();
829        if (activity != null) {
830            activity.invalidateOptionsMenu();
831        }
832
833        switch (state) {
834            case WifiManager.WIFI_STATE_ENABLED:
835                mScanner.resume();
836                return; // not break, to avoid the call to pause() below
837
838            case WifiManager.WIFI_STATE_ENABLING:
839                addMessagePreference(R.string.wifi_starting);
840                break;
841
842            case WifiManager.WIFI_STATE_DISABLED:
843                setOffMessage();
844                break;
845        }
846
847        mLastInfo = null;
848        mLastNetworkInfo = null;
849        mScanner.pause();
850    }
851
852    /**
853     * Renames/replaces "Next" button when appropriate. "Next" button usually exists in
854     * Wifi setup screens, not in usual wifi settings screen.
855     *
856     * @param enabled true when the device is connected to a wifi network.
857     */
858    private void changeNextButtonState(boolean enabled) {
859        if (mEnableNextOnConnection && hasNextButton()) {
860            getNextButton().setEnabled(enabled);
861        }
862    }
863
864    @Override
865    public void onClick(DialogInterface dialogInterface, int button) {
866        if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) {
867            forget();
868        } else if (button == WifiDialog.BUTTON_SUBMIT) {
869            if (mDialog != null) {
870                submit(mDialog.getController());
871            }
872        }
873    }
874
875    /* package */ void submit(WifiConfigController configController) {
876
877        final WifiConfiguration config = configController.getConfig();
878
879        if (config == null) {
880            if (mSelectedAccessPoint != null
881                    && mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
882                connect(mSelectedAccessPoint.networkId);
883            }
884        } else if (config.networkId != INVALID_NETWORK_ID) {
885            if (mSelectedAccessPoint != null) {
886                mWifiManager.save(config, mSaveListener);
887            }
888        } else {
889            if (configController.isEdit()) {
890                mWifiManager.save(config, mSaveListener);
891            } else {
892                connect(config);
893            }
894        }
895
896        if (mWifiManager.isWifiEnabled()) {
897            mScanner.resume();
898        }
899        updateAccessPoints();
900    }
901
902    /* package */ void forget() {
903        if (mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) {
904            if (mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED) {
905                // Network is active but has no network ID - must be ephemeral.
906                mWifiManager.disableEphemeralNetwork(
907                        AccessPoint.convertToQuotedString(mSelectedAccessPoint.ssid));
908            } else {
909                // Should not happen, but a monkey seems to trigger it
910                Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig());
911                return;
912            }
913        } else {
914            mWifiManager.forget(mSelectedAccessPoint.networkId, mForgetListener);
915        }
916
917
918        if (mWifiManager.isWifiEnabled()) {
919            mScanner.resume();
920        }
921        updateAccessPoints();
922
923        // We need to rename/replace "Next" button in wifi setup context.
924        changeNextButtonState(false);
925    }
926
927    protected void connect(final WifiConfiguration config) {
928        mWifiManager.connect(config, mConnectListener);
929    }
930
931    protected void connect(final int networkId) {
932        mWifiManager.connect(networkId, mConnectListener);
933    }
934
935    /**
936     * Refreshes acccess points and ask Wifi module to scan networks again.
937     */
938    /* package */ void refreshAccessPoints() {
939        if (mWifiManager.isWifiEnabled()) {
940            mScanner.resume();
941        }
942
943        getPreferenceScreen().removeAll();
944    }
945
946    /**
947     * Called when "add network" button is pressed.
948     */
949    /* package */ void onAddNetworkPressed() {
950        // No exact access point is selected.
951        mSelectedAccessPoint = null;
952        showDialog(null, true);
953    }
954
955    /* package */ int getAccessPointsCount() {
956        final boolean wifiIsEnabled = mWifiManager.isWifiEnabled();
957        if (wifiIsEnabled) {
958            return getPreferenceScreen().getPreferenceCount();
959        } else {
960            return 0;
961        }
962    }
963
964    /**
965     * Requests wifi module to pause wifi scan. May be ignored when the module is disabled.
966     */
967    /* package */ void pauseWifiScan() {
968        if (mWifiManager.isWifiEnabled()) {
969            mScanner.pause();
970        }
971    }
972
973    /**
974     * Requests wifi module to resume wifi scan. May be ignored when the module is disabled.
975     */
976    /* package */ void resumeWifiScan() {
977        if (mWifiManager.isWifiEnabled()) {
978            mScanner.resume();
979        }
980    }
981
982    @Override
983    protected int getHelpResource() {
984        return R.string.help_url_wifi;
985    }
986
987    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
988        new BaseSearchIndexProvider() {
989            @Override
990            public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
991                final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
992                final Resources res = context.getResources();
993
994                // Add fragment title
995                SearchIndexableRaw data = new SearchIndexableRaw(context);
996                data.title = res.getString(R.string.wifi_settings);
997                data.screenTitle = res.getString(R.string.wifi_settings);
998                data.keywords = res.getString(R.string.keywords_wifi);
999                result.add(data);
1000
1001                // Add available Wi-Fi access points
1002                WifiManager wifiManager =
1003                        (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
1004                final Collection<AccessPoint> accessPoints =
1005                        constructAccessPoints(context, wifiManager, null, null);
1006                for (AccessPoint accessPoint : accessPoints) {
1007                    // We are indexing only the saved Wi-Fi networks.
1008                    if (accessPoint.getConfig() == null) continue;
1009                    data = new SearchIndexableRaw(context);
1010                    data.title = accessPoint.getTitle().toString();
1011                    data.screenTitle = res.getString(R.string.wifi_settings);
1012                    data.enabled = enabled;
1013                    result.add(data);
1014                }
1015
1016                return result;
1017            }
1018        };
1019}
1020