WifiSettings.java revision 4cd0f1191b347df8cf0a550bcfd710c9c7c2bc13
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.telephony.TelephonyManager;
50import android.util.AttributeSet;
51import android.util.Log;
52import android.view.ContextMenu;
53import android.view.ContextMenu.ContextMenuInfo;
54import android.view.Gravity;
55import android.view.LayoutInflater;
56import android.view.Menu;
57import android.view.MenuInflater;
58import android.view.MenuItem;
59import android.view.View;
60import android.view.View.OnClickListener;
61import android.view.ViewGroup;
62import android.widget.AdapterView.AdapterContextMenuInfo;
63import android.widget.Button;
64import android.widget.ImageButton;
65import android.widget.PopupMenu;
66import android.widget.PopupMenu.OnMenuItemClickListener;
67import android.widget.RelativeLayout;
68import android.widget.Switch;
69import android.widget.TextView;
70import android.widget.Toast;
71
72import com.android.settings.R;
73import com.android.settings.SettingsPreferenceFragment;
74import com.android.settings.wifi.p2p.WifiP2pSettings;
75
76import java.util.ArrayList;
77import java.util.Collection;
78import java.util.Collections;
79import java.util.HashMap;
80import java.util.List;
81import java.util.concurrent.atomic.AtomicBoolean;
82
83/**
84 * Two types of UI are provided here.
85 *
86 * The first is for "usual Settings", appearing as any other Setup fragment.
87 *
88 * The second is for Setup Wizard, with a simplified interface that hides the action bar
89 * and menus.
90 */
91public class WifiSettings extends SettingsPreferenceFragment
92        implements DialogInterface.OnClickListener  {
93    private static final String TAG = "WifiSettings";
94    private static final int MENU_ID_WPS_PBC = Menu.FIRST;
95    private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1;
96    private static final int MENU_ID_P2P = Menu.FIRST + 2;
97    private static final int MENU_ID_ADD_NETWORK = Menu.FIRST + 3;
98    private static final int MENU_ID_ADVANCED = Menu.FIRST + 4;
99    private static final int MENU_ID_SCAN = Menu.FIRST + 5;
100    private static final int MENU_ID_CONNECT = Menu.FIRST + 6;
101    private static final int MENU_ID_FORGET = Menu.FIRST + 7;
102    private static final int MENU_ID_MODIFY = Menu.FIRST + 8;
103
104    private static final int WIFI_DIALOG_ID = 1;
105    private static final int WPS_PBC_DIALOG_ID = 2;
106    private static final int WPS_PIN_DIALOG_ID = 3;
107    private static final int WIFI_SKIPPED_DIALOG_ID = 4;
108    private static final int WIFI_AND_MOBILE_SKIPPED_DIALOG_ID = 5;
109
110    // Combo scans can take 5-6s to complete - set to 10s.
111    private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
112
113    // Instance state keys
114    private static final String SAVE_DIALOG_EDIT_MODE = "edit_mode";
115    private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
116
117    private final IntentFilter mFilter;
118    private final BroadcastReceiver mReceiver;
119    private final Scanner mScanner;
120
121    private WifiManager mWifiManager;
122    private WifiManager.ActionListener mConnectListener;
123    private WifiManager.ActionListener mSaveListener;
124    private WifiManager.ActionListener mForgetListener;
125    private boolean mP2pSupported;
126
127
128    private WifiEnabler mWifiEnabler;
129    // An access point being editted is stored here.
130    private AccessPoint mSelectedAccessPoint;
131
132    private DetailedState mLastState;
133    private WifiInfo mLastInfo;
134
135    private AtomicBoolean mConnected = new AtomicBoolean(false);
136
137    private WifiDialog mDialog;
138
139    private TextView mEmptyView;
140
141    /* Used in Wifi Setup context */
142
143    // this boolean extra specifies whether to disable the Next button when not connected
144    private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
145
146    // this boolean extra specifies whether to auto finish when connection is established
147    private static final String EXTRA_AUTO_FINISH_ON_CONNECT = "wifi_auto_finish_on_connect";
148
149    // this boolean extra shows a custom button that we can control
150    protected static final String EXTRA_SHOW_CUSTOM_BUTTON = "wifi_show_custom_button";
151
152    // show a text regarding data charges when wifi connection is required during setup wizard
153    protected static final String EXTRA_SHOW_WIFI_REQUIRED_INFO = "wifi_show_wifi_required_info";
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            Intent intent = getActivity().getIntent();
242            if (intent.getBooleanExtra(EXTRA_SHOW_CUSTOM_BUTTON, false)) {
243                view.findViewById(R.id.button_bar).setVisibility(View.VISIBLE);
244                view.findViewById(R.id.back_button).setVisibility(View.INVISIBLE);
245                view.findViewById(R.id.skip_button).setVisibility(View.INVISIBLE);
246                view.findViewById(R.id.next_button).setVisibility(View.INVISIBLE);
247
248                Button customButton = (Button) view.findViewById(R.id.custom_button);
249                customButton.setVisibility(View.VISIBLE);
250                customButton.setOnClickListener(new OnClickListener() {
251                    @Override
252                    public void onClick(View v) {
253                        if (isPhone() && !hasSimProblem()) {
254                            showDialog(WIFI_SKIPPED_DIALOG_ID);
255                        } else {
256                            showDialog(WIFI_AND_MOBILE_SKIPPED_DIALOG_ID);
257                        }
258                    }
259                });
260            }
261
262            if (intent.getBooleanExtra(EXTRA_SHOW_WIFI_REQUIRED_INFO, false)) {
263                view.findViewById(R.id.wifi_required_info).setVisibility(View.VISIBLE);
264            }
265
266            return view;
267        } else {
268            return super.onCreateView(inflater, container, savedInstanceState);
269        }
270    }
271
272    @Override
273    public void onActivityCreated(Bundle savedInstanceState) {
274        super.onActivityCreated(savedInstanceState);
275
276        mP2pSupported = getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT);
277        mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
278
279        mConnectListener = new WifiManager.ActionListener() {
280                                   public void onSuccess() {
281                                   }
282                                   public void onFailure(int reason) {
283                                       Activity activity = getActivity();
284                                       if (activity != null) {
285                                           Toast.makeText(activity,
286                                                R.string.wifi_failed_connect_message,
287                                                Toast.LENGTH_SHORT).show();
288                                       }
289                                   }
290                               };
291
292        mSaveListener = new WifiManager.ActionListener() {
293                                public void onSuccess() {
294                                }
295                                public void onFailure(int reason) {
296                                    Activity activity = getActivity();
297                                    if (activity != null) {
298                                        Toast.makeText(activity,
299                                            R.string.wifi_failed_save_message,
300                                            Toast.LENGTH_SHORT).show();
301                                    }
302                                }
303                            };
304
305        mForgetListener = new WifiManager.ActionListener() {
306                                   public void onSuccess() {
307                                   }
308                                   public void onFailure(int reason) {
309                                       Activity activity = getActivity();
310                                       if (activity != null) {
311                                           Toast.makeText(activity,
312                                               R.string.wifi_failed_forget_message,
313                                               Toast.LENGTH_SHORT).show();
314                                       }
315                                   }
316                               };
317
318        if (savedInstanceState != null
319                && savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
320            mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE);
321            mAccessPointSavedState = savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
322        }
323
324        final Activity activity = getActivity();
325        final Intent intent = activity.getIntent();
326
327        // first if we're supposed to finish once we have a connection
328        mAutoFinishOnConnection = intent.getBooleanExtra(EXTRA_AUTO_FINISH_ON_CONNECT, false);
329
330        if (mAutoFinishOnConnection) {
331            // Hide the next button
332            if (hasNextButton()) {
333                getNextButton().setVisibility(View.GONE);
334            }
335
336            final ConnectivityManager connectivity = (ConnectivityManager)
337                    activity.getSystemService(Context.CONNECTIVITY_SERVICE);
338            if (connectivity != null
339                    && connectivity.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected()) {
340                activity.setResult(Activity.RESULT_OK);
341                activity.finish();
342                return;
343            }
344        }
345
346        // if we're supposed to enable/disable the Next button based on our current connection
347        // state, start it off in the right state
348        mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
349
350        if (mEnableNextOnConnection) {
351            if (hasNextButton()) {
352                final ConnectivityManager connectivity = (ConnectivityManager)
353                        activity.getSystemService(Context.CONNECTIVITY_SERVICE);
354                if (connectivity != null) {
355                    NetworkInfo info = connectivity.getNetworkInfo(
356                            ConnectivityManager.TYPE_WIFI);
357                    changeNextButtonState(info.isConnected());
358                }
359            }
360        }
361
362        addPreferencesFromResource(R.xml.wifi_settings);
363
364        if (mSetupWizardMode) {
365            getView().setSystemUiVisibility(
366                    View.STATUS_BAR_DISABLE_BACK |
367                    View.STATUS_BAR_DISABLE_HOME |
368                    View.STATUS_BAR_DISABLE_RECENT |
369                    View.STATUS_BAR_DISABLE_NOTIFICATION_ALERTS |
370                    View.STATUS_BAR_DISABLE_CLOCK);
371        }
372
373        // On/off switch is hidden for Setup Wizard
374        if (!mSetupWizardMode) {
375            Switch actionBarSwitch = new Switch(activity);
376
377            if (activity instanceof PreferenceActivity) {
378                PreferenceActivity preferenceActivity = (PreferenceActivity) activity;
379                if (preferenceActivity.onIsHidingHeaders() || !preferenceActivity.onIsMultiPane()) {
380                    final int padding = activity.getResources().getDimensionPixelSize(
381                            R.dimen.action_bar_switch_padding);
382                    actionBarSwitch.setPaddingRelative(0, 0, padding, 0);
383                    activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM,
384                            ActionBar.DISPLAY_SHOW_CUSTOM);
385                    activity.getActionBar().setCustomView(actionBarSwitch, new ActionBar.LayoutParams(
386                            ActionBar.LayoutParams.WRAP_CONTENT,
387                            ActionBar.LayoutParams.WRAP_CONTENT,
388                            Gravity.CENTER_VERTICAL | Gravity.END));
389                }
390            }
391
392            mWifiEnabler = new WifiEnabler(activity, actionBarSwitch);
393        }
394
395        mEmptyView = (TextView) getView().findViewById(android.R.id.empty);
396        getListView().setEmptyView(mEmptyView);
397
398        if (!mSetupWizardMode) {
399            registerForContextMenu(getListView());
400        }
401        setHasOptionsMenu(true);
402    }
403
404    @Override
405    public void onResume() {
406        super.onResume();
407        if (mWifiEnabler != null) {
408            mWifiEnabler.resume();
409        }
410
411        getActivity().registerReceiver(mReceiver, mFilter);
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                    mWifiManager.connect(mSelectedAccessPoint.networkId,
555                            mConnectListener);
556                } else if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE) {
557                    /** Bypass dialog for unsecured networks */
558                    mSelectedAccessPoint.generateOpenNetworkConfig();
559                    mWifiManager.connect(mSelectedAccessPoint.getConfig(),
560                            mConnectListener);
561                } else {
562                    showDialog(mSelectedAccessPoint, true);
563                }
564                return true;
565            }
566            case MENU_ID_FORGET: {
567                mWifiManager.forget(mSelectedAccessPoint.networkId, mForgetListener);
568                return true;
569            }
570            case MENU_ID_MODIFY: {
571                showDialog(mSelectedAccessPoint, true);
572                return true;
573            }
574        }
575        return super.onContextItemSelected(item);
576    }
577
578    @Override
579    public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
580        if (preference instanceof AccessPoint) {
581            mSelectedAccessPoint = (AccessPoint) preference;
582            /** Bypass dialog for unsecured, unsaved networks */
583            if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE &&
584                    mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) {
585                mSelectedAccessPoint.generateOpenNetworkConfig();
586                mWifiManager.connect(mSelectedAccessPoint.getConfig(), mConnectListener);
587            } else {
588                showDialog(mSelectedAccessPoint, false);
589            }
590        } else {
591            return super.onPreferenceTreeClick(screen, preference);
592        }
593        return true;
594    }
595
596    private void showDialog(AccessPoint accessPoint, boolean edit) {
597        if (mDialog != null) {
598            removeDialog(WIFI_DIALOG_ID);
599            mDialog = null;
600        }
601
602        // Save the access point and edit mode
603        mDlgAccessPoint = accessPoint;
604        mDlgEdit = edit;
605
606        showDialog(WIFI_DIALOG_ID);
607    }
608
609    @Override
610    public Dialog onCreateDialog(int dialogId) {
611        switch (dialogId) {
612            case WIFI_DIALOG_ID:
613                AccessPoint ap = mDlgAccessPoint; // For manual launch
614                if (ap == null) { // For re-launch from saved state
615                    if (mAccessPointSavedState != null) {
616                        ap = new AccessPoint(getActivity(), mAccessPointSavedState);
617                        // For repeated orientation changes
618                        mDlgAccessPoint = ap;
619                    }
620                }
621                // If it's still null, fine, it's for Add Network
622                mSelectedAccessPoint = ap;
623                mDialog = new WifiDialog(getActivity(), this, ap, mDlgEdit);
624                return mDialog;
625            case WPS_PBC_DIALOG_ID:
626                return new WpsDialog(getActivity(), WpsInfo.PBC);
627            case WPS_PIN_DIALOG_ID:
628                return new WpsDialog(getActivity(), WpsInfo.DISPLAY);
629            case WIFI_SKIPPED_DIALOG_ID:
630                return new AlertDialog.Builder(getActivity())
631                            .setMessage(R.string.wifi_skipped_message)
632                            .setCancelable(false)
633                            .setNegativeButton(R.string.wifi_skip_anyway,
634                                    new DialogInterface.OnClickListener() {
635                                @Override
636                                public void onClick(DialogInterface dialog, int id) {
637                                    getActivity().setResult(Activity.RESULT_CANCELED);
638                                    getActivity().finish();
639                                }
640                            })
641                            .setPositiveButton(R.string.wifi_dont_skip,
642                                    new DialogInterface.OnClickListener() {
643                                @Override
644                                public void onClick(DialogInterface dialog, int id) {
645                                }
646                            })
647                            .create();
648            case WIFI_AND_MOBILE_SKIPPED_DIALOG_ID:
649                return new AlertDialog.Builder(getActivity())
650                            .setMessage(R.string.wifi_and_mobile_skipped_message)
651                            .setCancelable(false)
652                            .setNegativeButton(R.string.wifi_skip_anyway,
653                                    new DialogInterface.OnClickListener() {
654                                @Override
655                                public void onClick(DialogInterface dialog, int id) {
656                                    getActivity().setResult(Activity.RESULT_CANCELED);
657                                    getActivity().finish();
658                                }
659                            })
660                            .setPositiveButton(R.string.wifi_dont_skip,
661                                    new DialogInterface.OnClickListener() {
662                                @Override
663                                public void onClick(DialogInterface dialog, int id) {
664                                }
665                            })
666                            .create();
667
668        }
669        return super.onCreateDialog(dialogId);
670    }
671
672    private boolean isPhone() {
673        final TelephonyManager telephonyManager = (TelephonyManager)this.getSystemService(
674                Context.TELEPHONY_SERVICE);
675        return telephonyManager != null
676                && telephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE;
677    }
678
679    /**
680    * Return true if there's any SIM related impediment to connectivity.
681    * Treats Unknown as OK. (Only returns true if we're sure of a SIM problem.)
682    */
683   protected boolean hasSimProblem() {
684       final TelephonyManager telephonyManager = (TelephonyManager)this.getSystemService(
685               Context.TELEPHONY_SERVICE);
686       return telephonyManager != null
687               && telephonyManager.getCurrentPhoneType() == TelephonyManager.PHONE_TYPE_GSM
688               && telephonyManager.getSimState() != TelephonyManager.SIM_STATE_READY
689               && telephonyManager.getSimState() != TelephonyManager.SIM_STATE_UNKNOWN;
690   }
691
692    /**
693     * Shows the latest access points available with supplimental information like
694     * the strength of network and the security for it.
695     */
696    private void updateAccessPoints() {
697        // Safeguard from some delayed event handling
698        if (getActivity() == null) return;
699
700        final int wifiState = mWifiManager.getWifiState();
701
702        switch (wifiState) {
703            case WifiManager.WIFI_STATE_ENABLED:
704                // AccessPoints are automatically sorted with TreeSet.
705                final Collection<AccessPoint> accessPoints = constructAccessPoints();
706                getPreferenceScreen().removeAll();
707                if(accessPoints.size() == 0) {
708                    addMessagePreference(R.string.wifi_empty_list_wifi_on);
709                }
710                for (AccessPoint accessPoint : accessPoints) {
711                    getPreferenceScreen().addPreference(accessPoint);
712                }
713                break;
714
715            case WifiManager.WIFI_STATE_ENABLING:
716                getPreferenceScreen().removeAll();
717                break;
718
719            case WifiManager.WIFI_STATE_DISABLING:
720                addMessagePreference(R.string.wifi_stopping);
721                break;
722
723            case WifiManager.WIFI_STATE_DISABLED:
724                addMessagePreference(R.string.wifi_empty_list_wifi_off);
725                break;
726        }
727    }
728
729    private void addMessagePreference(int messageId) {
730        if (mEmptyView != null) mEmptyView.setText(messageId);
731        getPreferenceScreen().removeAll();
732    }
733
734    /** Returns sorted list of access points */
735    private List<AccessPoint> constructAccessPoints() {
736        ArrayList<AccessPoint> accessPoints = new ArrayList<AccessPoint>();
737        /** Lookup table to more quickly update AccessPoints by only considering objects with the
738         * correct SSID.  Maps SSID -> List of AccessPoints with the given SSID.  */
739        Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
740
741        final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks();
742        if (configs != null) {
743            for (WifiConfiguration config : configs) {
744                AccessPoint accessPoint = new AccessPoint(getActivity(), config);
745                accessPoint.update(mLastInfo, mLastState);
746                accessPoints.add(accessPoint);
747                apMap.put(accessPoint.ssid, accessPoint);
748            }
749        }
750
751        final List<ScanResult> results = mWifiManager.getScanResults();
752        if (results != null) {
753            for (ScanResult result : results) {
754                // Ignore hidden and ad-hoc networks.
755                if (result.SSID == null || result.SSID.length() == 0 ||
756                        result.capabilities.contains("[IBSS]")) {
757                    continue;
758                }
759
760                boolean found = false;
761                for (AccessPoint accessPoint : apMap.getAll(result.SSID)) {
762                    if (accessPoint.update(result))
763                        found = true;
764                }
765                if (!found) {
766                    AccessPoint accessPoint = new AccessPoint(getActivity(), result);
767                    accessPoints.add(accessPoint);
768                    apMap.put(accessPoint.ssid, accessPoint);
769                }
770            }
771        }
772
773        // Pre-sort accessPoints to speed preference insertion
774        Collections.sort(accessPoints);
775        return accessPoints;
776    }
777
778    /** A restricted multimap for use in constructAccessPoints */
779    private class Multimap<K,V> {
780        private HashMap<K,List<V>> store = new HashMap<K,List<V>>();
781        /** retrieve a non-null list of values with key K */
782        List<V> getAll(K key) {
783            List<V> values = store.get(key);
784            return values != null ? values : Collections.<V>emptyList();
785        }
786
787        void put(K key, V val) {
788            List<V> curVals = store.get(key);
789            if (curVals == null) {
790                curVals = new ArrayList<V>(3);
791                store.put(key, curVals);
792            }
793            curVals.add(val);
794        }
795    }
796
797    private void handleEvent(Context context, Intent intent) {
798        String action = intent.getAction();
799        if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
800            updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
801                    WifiManager.WIFI_STATE_UNKNOWN));
802        } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||
803                WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
804                WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
805                updateAccessPoints();
806        } else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) {
807            //Ignore supplicant state changes when network is connected
808            //TODO: we should deprecate SUPPLICANT_STATE_CHANGED_ACTION and
809            //introduce a broadcast that combines the supplicant and network
810            //network state change events so the apps dont have to worry about
811            //ignoring supplicant state change when network is connected
812            //to get more fine grained information.
813            SupplicantState state = (SupplicantState) intent.getParcelableExtra(
814                    WifiManager.EXTRA_NEW_STATE);
815            if (!mConnected.get() && SupplicantState.isHandshakeState(state)) {
816                updateConnectionState(WifiInfo.getDetailedStateOf(state));
817             } else {
818                 // During a connect, we may have the supplicant
819                 // state change affect the detailed network state.
820                 // Make sure a lost connection is updated as well.
821                 updateConnectionState(null);
822             }
823        } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
824            NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
825                    WifiManager.EXTRA_NETWORK_INFO);
826            mConnected.set(info.isConnected());
827            changeNextButtonState(info.isConnected());
828            updateAccessPoints();
829            updateConnectionState(info.getDetailedState());
830            if (mAutoFinishOnConnection && info.isConnected()) {
831                Activity activity = getActivity();
832                if (activity != null) {
833                    activity.setResult(Activity.RESULT_OK);
834                    activity.finish();
835                }
836                return;
837            }
838        } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
839            updateConnectionState(null);
840        }
841    }
842
843    private void updateConnectionState(DetailedState state) {
844        /* sticky broadcasts can call this when wifi is disabled */
845        if (!mWifiManager.isWifiEnabled()) {
846            mScanner.pause();
847            return;
848        }
849
850        if (state == DetailedState.OBTAINING_IPADDR) {
851            mScanner.pause();
852        } else {
853            mScanner.resume();
854        }
855
856        mLastInfo = mWifiManager.getConnectionInfo();
857        if (state != null) {
858            mLastState = state;
859        }
860
861        for (int i = getPreferenceScreen().getPreferenceCount() - 1; i >= 0; --i) {
862            // Maybe there's a WifiConfigPreference
863            Preference preference = getPreferenceScreen().getPreference(i);
864            if (preference instanceof AccessPoint) {
865                final AccessPoint accessPoint = (AccessPoint) preference;
866                accessPoint.update(mLastInfo, mLastState);
867            }
868        }
869    }
870
871    private void updateWifiState(int state) {
872        Activity activity = getActivity();
873        if (activity != null) {
874            activity.invalidateOptionsMenu();
875        }
876
877        switch (state) {
878            case WifiManager.WIFI_STATE_ENABLED:
879                mScanner.resume();
880                return; // not break, to avoid the call to pause() below
881
882            case WifiManager.WIFI_STATE_ENABLING:
883                addMessagePreference(R.string.wifi_starting);
884                break;
885
886            case WifiManager.WIFI_STATE_DISABLED:
887                addMessagePreference(R.string.wifi_empty_list_wifi_off);
888                break;
889        }
890
891        mLastInfo = null;
892        mLastState = null;
893        mScanner.pause();
894    }
895
896    private class Scanner extends Handler {
897        private int mRetry = 0;
898
899        void resume() {
900            if (!hasMessages(0)) {
901                sendEmptyMessage(0);
902            }
903        }
904
905        void forceScan() {
906            removeMessages(0);
907            sendEmptyMessage(0);
908        }
909
910        void pause() {
911            mRetry = 0;
912            removeMessages(0);
913        }
914
915        @Override
916        public void handleMessage(Message message) {
917            if (mWifiManager.startScanActive()) {
918                mRetry = 0;
919            } else if (++mRetry >= 3) {
920                mRetry = 0;
921                Activity activity = getActivity();
922                if (activity != null) {
923                    Toast.makeText(activity, R.string.wifi_fail_to_scan,
924                            Toast.LENGTH_LONG).show();
925                }
926                return;
927            }
928            sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
929        }
930    }
931
932    /**
933     * Renames/replaces "Next" button when appropriate. "Next" button usually exists in
934     * Wifi setup screens, not in usual wifi settings screen.
935     *
936     * @param connected true when the device is connected to a wifi network.
937     */
938    private void changeNextButtonState(boolean connected) {
939        if (mEnableNextOnConnection && hasNextButton()) {
940            getNextButton().setEnabled(connected);
941        }
942    }
943
944    public void onClick(DialogInterface dialogInterface, int button) {
945        if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) {
946            forget();
947        } else if (button == WifiDialog.BUTTON_SUBMIT) {
948            submit(mDialog.getController());
949        }
950    }
951
952    /* package */ void submit(WifiConfigController configController) {
953
954        final WifiConfiguration config = configController.getConfig();
955
956        if (config == null) {
957            if (mSelectedAccessPoint != null
958                    && mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
959                mWifiManager.connect(mSelectedAccessPoint.networkId,
960                        mConnectListener);
961            }
962        } else if (config.networkId != INVALID_NETWORK_ID) {
963            if (mSelectedAccessPoint != null) {
964                mWifiManager.save(config, mSaveListener);
965            }
966        } else {
967            if (configController.isEdit()) {
968                mWifiManager.save(config, mSaveListener);
969            } else {
970                mWifiManager.connect(config, mConnectListener);
971            }
972        }
973
974        if (mWifiManager.isWifiEnabled()) {
975            mScanner.resume();
976        }
977        updateAccessPoints();
978    }
979
980    /* package */ void forget() {
981        if (mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) {
982            // Should not happen, but a monkey seems to triger it
983            Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig());
984            return;
985        }
986
987        mWifiManager.forget(mSelectedAccessPoint.networkId, mForgetListener);
988
989        if (mWifiManager.isWifiEnabled()) {
990            mScanner.resume();
991        }
992        updateAccessPoints();
993
994        // We need to rename/replace "Next" button in wifi setup context.
995        changeNextButtonState(false);
996    }
997
998    /**
999     * Refreshes acccess points and ask Wifi module to scan networks again.
1000     */
1001    /* package */ void refreshAccessPoints() {
1002        if (mWifiManager.isWifiEnabled()) {
1003            mScanner.resume();
1004        }
1005
1006        getPreferenceScreen().removeAll();
1007    }
1008
1009    /**
1010     * Called when "add network" button is pressed.
1011     */
1012    /* package */ void onAddNetworkPressed() {
1013        // No exact access point is selected.
1014        mSelectedAccessPoint = null;
1015        showDialog(null, true);
1016    }
1017
1018    /* package */ int getAccessPointsCount() {
1019        final boolean wifiIsEnabled = mWifiManager.isWifiEnabled();
1020        if (wifiIsEnabled) {
1021            return getPreferenceScreen().getPreferenceCount();
1022        } else {
1023            return 0;
1024        }
1025    }
1026
1027    /**
1028     * Requests wifi module to pause wifi scan. May be ignored when the module is disabled.
1029     */
1030    /* package */ void pauseWifiScan() {
1031        if (mWifiManager.isWifiEnabled()) {
1032            mScanner.pause();
1033        }
1034    }
1035
1036    /**
1037     * Requests wifi module to resume wifi scan. May be ignored when the module is disabled.
1038     */
1039    /* package */ void resumeWifiScan() {
1040        if (mWifiManager.isWifiEnabled()) {
1041            mScanner.resume();
1042        }
1043    }
1044
1045    @Override
1046    protected int getHelpResource() {
1047        if (mSetupWizardMode) {
1048            return 0;
1049        }
1050        return R.string.help_url_wifi;
1051    }
1052
1053    /**
1054     * Used as the outer frame of all setup wizard pages that need to adjust their margins based
1055     * on the total size of the available display. (e.g. side margins set to 10% of total width.)
1056     */
1057    public static class ProportionalOuterFrame extends RelativeLayout {
1058        public ProportionalOuterFrame(Context context) {
1059            super(context);
1060        }
1061        public ProportionalOuterFrame(Context context, AttributeSet attrs) {
1062            super(context, attrs);
1063        }
1064        public ProportionalOuterFrame(Context context, AttributeSet attrs, int defStyle) {
1065            super(context, attrs, defStyle);
1066        }
1067
1068        /**
1069         * Set our margins and title area height proportionally to the available display size
1070         */
1071        @Override
1072        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1073            int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
1074            int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
1075            final Resources resources = getContext().getResources();
1076            float titleHeight = resources.getFraction(R.dimen.setup_title_height, 1, 1);
1077            float sideMargin = resources.getFraction(R.dimen.setup_border_width, 1, 1);
1078            int bottom = resources.getDimensionPixelSize(R.dimen.setup_margin_bottom);
1079            setPadding(
1080                    (int) (parentWidth * sideMargin),
1081                    0,
1082                    (int) (parentWidth * sideMargin),
1083                    bottom);
1084            View title = findViewById(R.id.title_area);
1085            if (title != null) {
1086                title.setMinimumHeight((int) (parentHeight * titleHeight));
1087            }
1088            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1089        }
1090    }
1091
1092}
1093