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