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