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