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