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