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