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