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