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