WifiSettings.java revision fc1281e6a80951321d9bc46c8eee27da1a4ba3ed
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;
20
21import android.app.ActionBar;
22import android.app.Activity;
23import android.app.AlertDialog;
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.pm.PackageManager;
31import android.net.ConnectivityManager;
32import android.net.NetworkInfo;
33import android.net.NetworkInfo.DetailedState;
34import android.net.wifi.ScanResult;
35import android.net.wifi.SupplicantState;
36import android.net.wifi.WifiConfiguration;
37import android.net.wifi.WifiConfiguration.KeyMgmt;
38import android.net.wifi.WifiInfo;
39import android.net.wifi.WifiManager;
40import android.net.wifi.WpsInfo;
41import android.os.Bundle;
42import android.os.Handler;
43import android.os.Message;
44import android.preference.Preference;
45import android.preference.PreferenceActivity;
46import android.preference.PreferenceScreen;
47import android.security.Credentials;
48import android.security.KeyStore;
49import android.util.Log;
50import android.view.ContextMenu;
51import android.view.ContextMenu.ContextMenuInfo;
52import android.view.Gravity;
53import android.view.Menu;
54import android.view.MenuInflater;
55import android.view.MenuItem;
56import android.view.View;
57import android.widget.AdapterView.AdapterContextMenuInfo;
58import android.widget.Switch;
59import android.widget.TextView;
60import android.widget.Toast;
61
62import com.android.settings.R;
63import com.android.settings.SettingsPreferenceFragment;
64import com.android.settings.wifi.p2p.WifiP2pSettings;
65
66import java.util.ArrayList;
67import java.util.Collection;
68import java.util.Collections;
69import java.util.HashMap;
70import java.util.List;
71import java.util.concurrent.atomic.AtomicBoolean;
72
73/**
74 * Two types of UI are provided here.
75 *
76 * The first is for "usual Settings", appearing as any other Setup fragment.
77 *
78 * The second is for Setup Wizard, with a simplified interface that hides the action bar
79 * and menus.
80 */
81public class WifiSettings extends SettingsPreferenceFragment
82        implements DialogInterface.OnClickListener  {
83    private static final String TAG = "WifiSettings";
84    private static final int MENU_ID_WPS_PBC = Menu.FIRST;
85    private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1;
86    private static final int MENU_ID_P2P = Menu.FIRST + 2;
87    private static final int MENU_ID_ADD_NETWORK = Menu.FIRST + 3;
88    private static final int MENU_ID_ADVANCED = Menu.FIRST + 4;
89    private static final int MENU_ID_SCAN = Menu.FIRST + 5;
90    private static final int MENU_ID_CONNECT = Menu.FIRST + 6;
91    private static final int MENU_ID_FORGET = Menu.FIRST + 7;
92    private static final int MENU_ID_MODIFY = Menu.FIRST + 8;
93
94    private static final int WIFI_DIALOG_ID = 1;
95    private static final int WPS_PBC_DIALOG_ID = 2;
96    private static final int WPS_PIN_DIALOG_ID = 3;
97
98    // Combo scans can take 5-6s to complete - set to 10s.
99    private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
100
101    // Instance state keys
102    private static final String SAVE_DIALOG_EDIT_MODE = "edit_mode";
103    private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
104
105    private final IntentFilter mFilter;
106    private final BroadcastReceiver mReceiver;
107    private final Scanner mScanner;
108
109    private WifiManager mWifiManager;
110    private WifiManager.Channel mChannel;
111    private WifiManager.ActionListener mConnectListener;
112    private WifiManager.ActionListener mSaveListener;
113    private WifiManager.ActionListener mForgetListener;
114    private boolean mP2pSupported;
115
116
117    private WifiEnabler mWifiEnabler;
118    // An access point being editted is stored here.
119    private AccessPoint mSelectedAccessPoint;
120
121    private DetailedState mLastState;
122    private WifiInfo mLastInfo;
123
124    private AtomicBoolean mConnected = new AtomicBoolean(false);
125
126    private int mKeyStoreNetworkId = INVALID_NETWORK_ID;
127
128    private WifiDialog mDialog;
129
130    private TextView mEmptyView;
131
132    /* Used in Wifi Setup context */
133
134    // this boolean extra specifies whether to disable the Next button when not connected
135    private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
136
137    // this boolean extra specifies whether to auto finish when connection is established
138    private static final String EXTRA_AUTO_FINISH_ON_CONNECT = "wifi_auto_finish_on_connect";
139
140    // this boolean extra is set if we are being invoked by the Setup Wizard
141    private static final String EXTRA_IS_FIRST_RUN = "firstRun";
142
143    private static final String EXTRA_WIFI_DISABLE_BACK = "wifi_disable_back";
144
145    // should Next button only be enabled when we have a connection?
146    private boolean mEnableNextOnConnection;
147
148    // should activity finish once we have a connection?
149    private boolean mAutoFinishOnConnection;
150
151    // Save the dialog details
152    private boolean mDlgEdit;
153    private AccessPoint mDlgAccessPoint;
154    private Bundle mAccessPointSavedState;
155
156    // the action bar uses a different set of controls for Setup Wizard
157    private boolean mSetupWizardMode;
158
159    /* End of "used in Wifi Setup context" */
160
161    public WifiSettings() {
162        mFilter = new IntentFilter();
163        mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
164        mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
165        mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
166        mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
167        mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
168        mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
169        mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
170        mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
171
172        mReceiver = new BroadcastReceiver() {
173            @Override
174            public void onReceive(Context context, Intent intent) {
175                handleEvent(context, intent);
176            }
177        };
178
179        mScanner = new Scanner();
180    }
181
182    @Override
183    public void onCreate(Bundle icicle) {
184        // Set this flag early, as it's needed by getHelpResource(), which is called by super
185        mSetupWizardMode = getActivity().getIntent().getBooleanExtra(EXTRA_IS_FIRST_RUN, false);
186
187        super.onCreate(icicle);
188    }
189
190    @Override
191    public void onActivityCreated(Bundle savedInstanceState) {
192        // We don't call super.onActivityCreated() here, since it assumes we already set up
193        // Preference (probably in onCreate()), while WifiSettings exceptionally set it up in
194        // this method.
195
196        mP2pSupported = getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT);
197        mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
198        mChannel = mWifiManager.initialize(getActivity(), getActivity().getMainLooper(), null);
199
200        mConnectListener = new WifiManager.ActionListener() {
201                                   public void onSuccess() {
202                                   }
203                                   public void onFailure(int reason) {
204                                        Toast.makeText(getActivity(),
205                                            R.string.wifi_failed_connect_message,
206                                            Toast.LENGTH_SHORT).show();
207                                   }
208                               };
209
210        mSaveListener = new WifiManager.ActionListener() {
211                                public void onSuccess() {
212                                }
213                                public void onFailure(int reason) {
214                                    Toast.makeText(getActivity(),
215                                        R.string.wifi_failed_save_message,
216                                        Toast.LENGTH_SHORT).show();
217                                }
218                            };
219
220        mForgetListener = new WifiManager.ActionListener() {
221                                   public void onSuccess() {
222                                   }
223                                   public void onFailure(int reason) {
224                                        Toast.makeText(getActivity(),
225                                            R.string.wifi_failed_forget_message,
226                                            Toast.LENGTH_SHORT).show();
227                                   }
228                               };
229
230        if (savedInstanceState != null
231                && savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
232            mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE);
233            mAccessPointSavedState = savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
234        }
235
236        final Activity activity = getActivity();
237        final Intent intent = activity.getIntent();
238
239        // first if we're supposed to finish once we have a connection
240        mAutoFinishOnConnection = intent.getBooleanExtra(EXTRA_AUTO_FINISH_ON_CONNECT, false);
241
242        if (mAutoFinishOnConnection) {
243            // Hide the next button
244            if (hasNextButton()) {
245                getNextButton().setVisibility(View.GONE);
246            }
247
248            final ConnectivityManager connectivity = (ConnectivityManager)
249                    getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
250            if (connectivity != null
251                    && connectivity.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected()) {
252                activity.finish();
253                return;
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        mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
260
261        if (mEnableNextOnConnection) {
262            if (hasNextButton()) {
263                final ConnectivityManager connectivity = (ConnectivityManager)
264                        getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
265                if (connectivity != null) {
266                    NetworkInfo info = connectivity.getNetworkInfo(
267                            ConnectivityManager.TYPE_WIFI);
268                    changeNextButtonState(info.isConnected());
269                }
270            }
271        }
272
273        addPreferencesFromResource(R.xml.wifi_settings);
274
275        // Back key is disabled if requested
276        if (intent.getBooleanExtra(EXTRA_WIFI_DISABLE_BACK, false)) {
277            getView().setSystemUiVisibility(View.STATUS_BAR_DISABLE_BACK);
278        }
279
280        // On/off switch is hidden for Setup Wizard
281        if (!mSetupWizardMode) {
282            Switch actionBarSwitch = new Switch(activity);
283
284            if (activity instanceof PreferenceActivity) {
285                PreferenceActivity preferenceActivity = (PreferenceActivity) activity;
286                if (preferenceActivity.onIsHidingHeaders() || !preferenceActivity.onIsMultiPane()) {
287                    final int padding = activity.getResources().getDimensionPixelSize(
288                            R.dimen.action_bar_switch_padding);
289                    actionBarSwitch.setPadding(0, 0, padding, 0);
290                    activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
291                            ActionBar.DISPLAY_SHOW_CUSTOM);
292                    activity.getActionBar().setCustomView(actionBarSwitch, new ActionBar.LayoutParams(
293                            ActionBar.LayoutParams.WRAP_CONTENT,
294                            ActionBar.LayoutParams.WRAP_CONTENT,
295                            Gravity.CENTER_VERTICAL | Gravity.RIGHT));
296                }
297            }
298
299            mWifiEnabler = new WifiEnabler(activity, actionBarSwitch);
300        }
301
302        mEmptyView = (TextView) getView().findViewById(android.R.id.empty);
303        getListView().setEmptyView(mEmptyView);
304
305        if (!mSetupWizardMode) {
306            registerForContextMenu(getListView());
307        }
308        setHasOptionsMenu(true);
309
310        // After confirming PreferenceScreen is available, we call super.
311        super.onActivityCreated(savedInstanceState);
312    }
313
314    @Override
315    public void onResume() {
316        super.onResume();
317        if (mWifiEnabler != null) {
318            mWifiEnabler.resume();
319        }
320
321        getActivity().registerReceiver(mReceiver, mFilter);
322        if (mKeyStoreNetworkId != INVALID_NETWORK_ID &&
323                KeyStore.getInstance().state() == KeyStore.State.UNLOCKED) {
324            mWifiManager.connect(mChannel, mKeyStoreNetworkId, mConnectListener);
325        }
326        mKeyStoreNetworkId = INVALID_NETWORK_ID;
327
328        updateAccessPoints();
329    }
330
331    @Override
332    public void onPause() {
333        super.onPause();
334        if (mWifiEnabler != null) {
335            mWifiEnabler.pause();
336        }
337        getActivity().unregisterReceiver(mReceiver);
338        mScanner.pause();
339    }
340
341    @Override
342    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
343        final boolean wifiIsEnabled = mWifiManager.isWifiEnabled();
344        if (mSetupWizardMode) {
345            // FIXME: add setIcon() when graphics are available
346            menu.add(Menu.NONE, MENU_ID_WPS_PBC, 0, R.string.wifi_menu_wps_pbc)
347                    .setIcon(R.drawable.ic_wps)
348                    .setEnabled(wifiIsEnabled)
349                    .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
350            menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network)
351                    .setEnabled(wifiIsEnabled)
352                    .setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS);
353        } else {
354            menu.add(Menu.NONE, MENU_ID_WPS_PBC, 0, R.string.wifi_menu_wps_pbc)
355                    .setEnabled(wifiIsEnabled)
356                    .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
357            if (mP2pSupported) {
358                menu.add(Menu.NONE, MENU_ID_P2P, 0, R.string.wifi_menu_p2p)
359                        .setEnabled(wifiIsEnabled)
360                        .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
361            }
362            menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network)
363                    .setEnabled(wifiIsEnabled)
364                    .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
365            menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.wifi_menu_scan)
366                    //.setIcon(R.drawable.ic_menu_scan_network)
367                    .setEnabled(wifiIsEnabled)
368                    .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
369            menu.add(Menu.NONE, MENU_ID_WPS_PIN, 0, R.string.wifi_menu_wps_pin)
370                    .setEnabled(wifiIsEnabled)
371                    .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
372            menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
373                    //.setIcon(android.R.drawable.ic_menu_manage)
374                    .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
375        }
376        super.onCreateOptionsMenu(menu, inflater);
377    }
378
379    @Override
380    public void onSaveInstanceState(Bundle outState) {
381        super.onSaveInstanceState(outState);
382
383        // If the dialog is showing, save its state.
384        if (mDialog != null && mDialog.isShowing()) {
385            outState.putBoolean(SAVE_DIALOG_EDIT_MODE, mDlgEdit);
386            if (mDlgAccessPoint != null) {
387                mAccessPointSavedState = new Bundle();
388                mDlgAccessPoint.saveWifiState(mAccessPointSavedState);
389                outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
390            }
391        }
392    }
393
394    @Override
395    public boolean onOptionsItemSelected(MenuItem item) {
396        switch (item.getItemId()) {
397            case MENU_ID_WPS_PBC:
398                showDialog(WPS_PBC_DIALOG_ID);
399                return true;
400            case MENU_ID_P2P:
401                if (getActivity() instanceof PreferenceActivity) {
402                    ((PreferenceActivity) getActivity()).startPreferencePanel(
403                            WifiP2pSettings.class.getCanonicalName(),
404                            null,
405                            R.string.wifi_p2p_settings_title, null,
406                            this, 0);
407                } else {
408                    startFragment(this, WifiP2pSettings.class.getCanonicalName(), -1, null);
409                }
410                return true;
411            case MENU_ID_WPS_PIN:
412                showDialog(WPS_PIN_DIALOG_ID);
413                return true;
414            case MENU_ID_SCAN:
415                if (mWifiManager.isWifiEnabled()) {
416                    mScanner.forceScan();
417                }
418                return true;
419            case MENU_ID_ADD_NETWORK:
420                if (mWifiManager.isWifiEnabled()) {
421                    onAddNetworkPressed();
422                }
423                return true;
424            case MENU_ID_ADVANCED:
425                if (getActivity() instanceof PreferenceActivity) {
426                    ((PreferenceActivity) getActivity()).startPreferencePanel(
427                            AdvancedWifiSettings.class.getCanonicalName(),
428                            null,
429                            R.string.wifi_advanced_titlebar, null,
430                            this, 0);
431                } else {
432                    startFragment(this, AdvancedWifiSettings.class.getCanonicalName(), -1, null);
433                }
434                return true;
435        }
436        return super.onOptionsItemSelected(item);
437    }
438
439    @Override
440    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
441        if (info instanceof AdapterContextMenuInfo) {
442            Preference preference = (Preference) getListView().getItemAtPosition(
443                    ((AdapterContextMenuInfo) info).position);
444
445            if (preference instanceof AccessPoint) {
446                mSelectedAccessPoint = (AccessPoint) preference;
447                menu.setHeaderTitle(mSelectedAccessPoint.ssid);
448                if (mSelectedAccessPoint.getLevel() != -1
449                        && mSelectedAccessPoint.getState() == null) {
450                    menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
451                }
452                if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
453                    menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
454                    menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
455                }
456            }
457        }
458    }
459
460    @Override
461    public boolean onContextItemSelected(MenuItem item) {
462        if (mSelectedAccessPoint == null) {
463            return super.onContextItemSelected(item);
464        }
465        switch (item.getItemId()) {
466            case MENU_ID_CONNECT: {
467                if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
468                    if (!requireKeyStore(mSelectedAccessPoint.getConfig())) {
469                        mWifiManager.connect(mChannel, mSelectedAccessPoint.networkId,
470                                mConnectListener);
471                    }
472                } else if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE) {
473                    /** Bypass dialog for unsecured networks */
474                    mSelectedAccessPoint.generateOpenNetworkConfig();
475                    mWifiManager.connect(mChannel, mSelectedAccessPoint.getConfig(),
476                            mConnectListener);
477                } else {
478                    showDialog(mSelectedAccessPoint, true);
479                }
480                return true;
481            }
482            case MENU_ID_FORGET: {
483                mWifiManager.forget(mChannel, mSelectedAccessPoint.networkId, mForgetListener);
484                return true;
485            }
486            case MENU_ID_MODIFY: {
487                showDialog(mSelectedAccessPoint, true);
488                return true;
489            }
490        }
491        return super.onContextItemSelected(item);
492    }
493
494    @Override
495    public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
496        if (preference instanceof AccessPoint) {
497            mSelectedAccessPoint = (AccessPoint) preference;
498            /** Bypass dialog for unsecured, unsaved networks */
499            if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE &&
500                    mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) {
501                mSelectedAccessPoint.generateOpenNetworkConfig();
502                mWifiManager.connect(mChannel, mSelectedAccessPoint.getConfig(), mConnectListener);
503            } else {
504                showDialog(mSelectedAccessPoint, false);
505            }
506        } else {
507            return super.onPreferenceTreeClick(screen, preference);
508        }
509        return true;
510    }
511
512    private void showDialog(AccessPoint accessPoint, boolean edit) {
513        if (mDialog != null) {
514            removeDialog(WIFI_DIALOG_ID);
515            mDialog = null;
516        }
517
518        // Save the access point and edit mode
519        mDlgAccessPoint = accessPoint;
520        mDlgEdit = edit;
521
522        showDialog(WIFI_DIALOG_ID);
523    }
524
525    @Override
526    public Dialog onCreateDialog(int dialogId) {
527        switch (dialogId) {
528            case WIFI_DIALOG_ID:
529                AccessPoint ap = mDlgAccessPoint; // For manual launch
530                if (ap == null) { // For re-launch from saved state
531                    if (mAccessPointSavedState != null) {
532                        ap = new AccessPoint(getActivity(), mAccessPointSavedState);
533                        // For repeated orientation changes
534                        mDlgAccessPoint = ap;
535                    }
536                }
537                // If it's still null, fine, it's for Add Network
538                mSelectedAccessPoint = ap;
539                mDialog = new WifiDialog(getActivity(), this, ap, mDlgEdit);
540                return mDialog;
541            case WPS_PBC_DIALOG_ID:
542                return new WpsDialog(getActivity(), WpsInfo.PBC);
543            case WPS_PIN_DIALOG_ID:
544                return new WpsDialog(getActivity(), WpsInfo.DISPLAY);
545        }
546        return super.onCreateDialog(dialogId);
547    }
548
549    private boolean requireKeyStore(WifiConfiguration config) {
550        if (WifiConfigController.requireKeyStore(config) &&
551                KeyStore.getInstance().state() != KeyStore.State.UNLOCKED) {
552            mKeyStoreNetworkId = config.networkId;
553            Credentials.getInstance().unlock(getActivity());
554            return true;
555        }
556        return false;
557    }
558
559    /**
560     * Shows the latest access points available with supplimental information like
561     * the strength of network and the security for it.
562     */
563    private void updateAccessPoints() {
564        // Safeguard from some delayed event handling
565        if (getActivity() == null) return;
566
567        final int wifiState = mWifiManager.getWifiState();
568
569        switch (wifiState) {
570            case WifiManager.WIFI_STATE_ENABLED:
571                // AccessPoints are automatically sorted with TreeSet.
572                final Collection<AccessPoint> accessPoints = constructAccessPoints();
573                getPreferenceScreen().removeAll();
574                if(accessPoints.size() == 0) {
575                    addMessagePreference(R.string.wifi_empty_list_wifi_on);
576                }
577                for (AccessPoint accessPoint : accessPoints) {
578                    getPreferenceScreen().addPreference(accessPoint);
579                }
580                break;
581
582            case WifiManager.WIFI_STATE_ENABLING:
583                getPreferenceScreen().removeAll();
584                break;
585
586            case WifiManager.WIFI_STATE_DISABLING:
587                addMessagePreference(R.string.wifi_stopping);
588                break;
589
590            case WifiManager.WIFI_STATE_DISABLED:
591                addMessagePreference(R.string.wifi_empty_list_wifi_off);
592                break;
593        }
594    }
595
596    private void addMessagePreference(int messageId) {
597        if (mEmptyView != null) mEmptyView.setText(messageId);
598        getPreferenceScreen().removeAll();
599    }
600
601    /** Returns sorted list of access points */
602    private List<AccessPoint> constructAccessPoints() {
603        ArrayList<AccessPoint> accessPoints = new ArrayList<AccessPoint>();
604        /** Lookup table to more quickly update AccessPoints by only considering objects with the
605         * correct SSID.  Maps SSID -> List of AccessPoints with the given SSID.  */
606        Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
607
608        final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
609        if (configs != null) {
610            for (WifiConfiguration config : configs) {
611                AccessPoint accessPoint = new AccessPoint(getActivity(), config);
612                accessPoint.update(mLastInfo, mLastState);
613                accessPoints.add(accessPoint);
614                apMap.put(accessPoint.ssid, accessPoint);
615            }
616        }
617
618        final List<ScanResult> results = mWifiManager.getScanResults();
619        if (results != null) {
620            for (ScanResult result : results) {
621                // Ignore hidden and ad-hoc networks.
622                if (result.SSID == null || result.SSID.length() == 0 ||
623                        result.capabilities.contains("[IBSS]")) {
624                    continue;
625                }
626
627                boolean found = false;
628                for (AccessPoint accessPoint : apMap.getAll(result.SSID)) {
629                    if (accessPoint.update(result))
630                        found = true;
631                }
632                if (!found) {
633                    AccessPoint accessPoint = new AccessPoint(getActivity(), result);
634                    accessPoints.add(accessPoint);
635                    apMap.put(accessPoint.ssid, accessPoint);
636                }
637            }
638        }
639
640        // Pre-sort accessPoints to speed preference insertion
641        Collections.sort(accessPoints);
642        return accessPoints;
643    }
644
645    /** A restricted multimap for use in constructAccessPoints */
646    private class Multimap<K,V> {
647        private HashMap<K,List<V>> store = new HashMap<K,List<V>>();
648        /** retrieve a non-null list of values with key K */
649        List<V> getAll(K key) {
650            List<V> values = store.get(key);
651            return values != null ? values : Collections.<V>emptyList();
652        }
653
654        void put(K key, V val) {
655            List<V> curVals = store.get(key);
656            if (curVals == null) {
657                curVals = new ArrayList<V>(3);
658                store.put(key, curVals);
659            }
660            curVals.add(val);
661        }
662    }
663
664    private void handleEvent(Context context, Intent intent) {
665        String action = intent.getAction();
666        if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
667            updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
668                    WifiManager.WIFI_STATE_UNKNOWN));
669        } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||
670                WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
671                WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
672                updateAccessPoints();
673        } else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) {
674            //Ignore supplicant state changes when network is connected
675            //TODO: we should deprecate SUPPLICANT_STATE_CHANGED_ACTION and
676            //introduce a broadcast that combines the supplicant and network
677            //network state change events so the apps dont have to worry about
678            //ignoring supplicant state change when network is connected
679            //to get more fine grained information.
680            SupplicantState state = (SupplicantState) intent.getParcelableExtra(
681                    WifiManager.EXTRA_NEW_STATE);
682            if (!mConnected.get() && SupplicantState.isHandshakeState(state)) {
683                updateConnectionState(WifiInfo.getDetailedStateOf(state));
684            }
685        } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
686            NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
687                    WifiManager.EXTRA_NETWORK_INFO);
688            mConnected.set(info.isConnected());
689            changeNextButtonState(info.isConnected());
690            updateAccessPoints();
691            updateConnectionState(info.getDetailedState());
692            if (mAutoFinishOnConnection && info.isConnected()) {
693                getActivity().finish();
694                return;
695            }
696        } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
697            updateConnectionState(null);
698        }
699    }
700
701    private void updateConnectionState(DetailedState state) {
702        /* sticky broadcasts can call this when wifi is disabled */
703        if (!mWifiManager.isWifiEnabled()) {
704            mScanner.pause();
705            return;
706        }
707
708        if (state == DetailedState.OBTAINING_IPADDR) {
709            mScanner.pause();
710        } else {
711            mScanner.resume();
712        }
713
714        mLastInfo = mWifiManager.getConnectionInfo();
715        if (state != null) {
716            mLastState = state;
717        }
718
719        for (int i = getPreferenceScreen().getPreferenceCount() - 1; i >= 0; --i) {
720            // Maybe there's a WifiConfigPreference
721            Preference preference = getPreferenceScreen().getPreference(i);
722            if (preference instanceof AccessPoint) {
723                final AccessPoint accessPoint = (AccessPoint) preference;
724                accessPoint.update(mLastInfo, mLastState);
725            }
726        }
727    }
728
729    private void updateWifiState(int state) {
730        getActivity().invalidateOptionsMenu();
731
732        switch (state) {
733            case WifiManager.WIFI_STATE_ENABLED:
734                mScanner.resume();
735                return; // not break, to avoid the call to pause() below
736
737            case WifiManager.WIFI_STATE_ENABLING:
738                addMessagePreference(R.string.wifi_starting);
739                break;
740
741            case WifiManager.WIFI_STATE_DISABLED:
742                addMessagePreference(R.string.wifi_empty_list_wifi_off);
743                break;
744        }
745
746        mLastInfo = null;
747        mLastState = null;
748        mScanner.pause();
749    }
750
751    private class Scanner extends Handler {
752        private int mRetry = 0;
753
754        void resume() {
755            if (!hasMessages(0)) {
756                sendEmptyMessage(0);
757            }
758        }
759
760        void forceScan() {
761            removeMessages(0);
762            sendEmptyMessage(0);
763        }
764
765        void pause() {
766            mRetry = 0;
767            removeMessages(0);
768        }
769
770        @Override
771        public void handleMessage(Message message) {
772            if (mWifiManager.startScanActive()) {
773                mRetry = 0;
774            } else if (++mRetry >= 3) {
775                mRetry = 0;
776                Toast.makeText(getActivity(), R.string.wifi_fail_to_scan,
777                        Toast.LENGTH_LONG).show();
778                return;
779            }
780            sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
781        }
782    }
783
784    /**
785     * Renames/replaces "Next" button when appropriate. "Next" button usually exists in
786     * Wifi setup screens, not in usual wifi settings screen.
787     *
788     * @param connected true when the device is connected to a wifi network.
789     */
790    private void changeNextButtonState(boolean connected) {
791        if (mEnableNextOnConnection && hasNextButton()) {
792            getNextButton().setEnabled(connected);
793        }
794    }
795
796    public void onClick(DialogInterface dialogInterface, int button) {
797        if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) {
798            forget();
799        } else if (button == WifiDialog.BUTTON_SUBMIT) {
800            submit(mDialog.getController());
801        }
802    }
803
804    /* package */ void submit(WifiConfigController configController) {
805
806        final WifiConfiguration config = configController.getConfig();
807
808        if (config == null) {
809            if (mSelectedAccessPoint != null
810                    && !requireKeyStore(mSelectedAccessPoint.getConfig())
811                    && mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
812                mWifiManager.connect(mChannel, mSelectedAccessPoint.networkId,
813                        mConnectListener);
814            }
815        } else if (config.networkId != INVALID_NETWORK_ID) {
816            if (mSelectedAccessPoint != null) {
817                mWifiManager.save(mChannel, config, mSaveListener);
818            }
819        } else {
820            if (configController.isEdit() || requireKeyStore(config)) {
821                mWifiManager.save(mChannel, config, mSaveListener);
822            } else {
823                mWifiManager.connect(mChannel, config, mConnectListener);
824            }
825        }
826
827        if (mWifiManager.isWifiEnabled()) {
828            mScanner.resume();
829        }
830        updateAccessPoints();
831    }
832
833    /* package */ void forget() {
834        if (mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) {
835            // Should not happen, but a monkey seems to triger it
836            Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig());
837            return;
838        }
839
840        mWifiManager.forget(mChannel, mSelectedAccessPoint.networkId, mForgetListener);
841
842        if (mWifiManager.isWifiEnabled()) {
843            mScanner.resume();
844        }
845        updateAccessPoints();
846
847        // We need to rename/replace "Next" button in wifi setup context.
848        changeNextButtonState(false);
849    }
850
851    /**
852     * Refreshes acccess points and ask Wifi module to scan networks again.
853     */
854    /* package */ void refreshAccessPoints() {
855        if (mWifiManager.isWifiEnabled()) {
856            mScanner.resume();
857        }
858
859        getPreferenceScreen().removeAll();
860    }
861
862    /**
863     * Called when "add network" button is pressed.
864     */
865    /* package */ void onAddNetworkPressed() {
866        // No exact access point is selected.
867        mSelectedAccessPoint = null;
868        showDialog(null, true);
869    }
870
871    /* package */ int getAccessPointsCount() {
872        final boolean wifiIsEnabled = mWifiManager.isWifiEnabled();
873        if (wifiIsEnabled) {
874            return getPreferenceScreen().getPreferenceCount();
875        } else {
876            return 0;
877        }
878    }
879
880    /**
881     * Requests wifi module to pause wifi scan. May be ignored when the module is disabled.
882     */
883    /* package */ void pauseWifiScan() {
884        if (mWifiManager.isWifiEnabled()) {
885            mScanner.pause();
886        }
887    }
888
889    /**
890     * Requests wifi module to resume wifi scan. May be ignored when the module is disabled.
891     */
892    /* package */ void resumeWifiScan() {
893        if (mWifiManager.isWifiEnabled()) {
894            mScanner.resume();
895        }
896    }
897
898    @Override
899    protected int getHelpResource() {
900        if (mSetupWizardMode) {
901            return 0;
902        }
903        return R.string.help_url_wifi;
904    }
905}
906