WifiSettings.java revision f11feccf99948d166e6873315252fdaf416ae272
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.Activity;
23import android.app.ActivityManager;
24import android.app.Dialog;
25import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.DialogInterface;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.res.Resources;
31import android.content.res.TypedArray;
32import android.location.LocationManager;
33import android.net.ConnectivityManager;
34import android.net.NetworkInfo;
35import android.net.NetworkInfo.DetailedState;
36import android.net.NetworkInfo.State;
37import android.net.wifi.ScanResult;
38import android.net.wifi.WifiConfiguration;
39import android.net.wifi.WifiInfo;
40import android.net.wifi.WifiManager;
41import android.net.wifi.WpsInfo;
42import android.nfc.NfcAdapter;
43import android.os.Bundle;
44import android.os.Handler;
45import android.os.Message;
46import android.os.UserHandle;
47import android.preference.Preference;
48import android.preference.PreferenceScreen;
49import android.util.Log;
50import android.view.ContextMenu;
51import android.view.ContextMenu.ContextMenuInfo;
52import android.view.Menu;
53import android.view.MenuInflater;
54import android.view.MenuItem;
55import android.view.View;
56import android.widget.AdapterView.AdapterContextMenuInfo;
57import android.widget.TextView;
58import android.widget.Toast;
59
60import com.android.settings.R;
61import com.android.settings.RestrictedSettingsFragment;
62import com.android.settings.SettingsActivity;
63import com.android.settings.search.BaseSearchIndexProvider;
64import com.android.settings.search.Indexable;
65import com.android.settings.search.SearchIndexableRaw;
66
67import java.util.ArrayList;
68import java.util.Collection;
69import java.util.Collections;
70import java.util.HashMap;
71import java.util.List;
72import java.util.concurrent.atomic.AtomicBoolean;
73
74/**
75 * Two types of UI are provided here.
76 *
77 * The first is for "usual Settings", appearing as any other Setup fragment.
78 *
79 * The second is for Setup Wizard, with a simplified interface that hides the action bar
80 * and menus.
81 */
82public class WifiSettings extends RestrictedSettingsFragment
83        implements DialogInterface.OnClickListener, Indexable  {
84
85    private static final String TAG = "WifiSettings";
86
87    /* package */ static final int MENU_ID_WPS_PBC = Menu.FIRST;
88    private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1;
89    private static final int MENU_ID_SAVED_NETWORK = Menu.FIRST + 2;
90    /* package */ static final int MENU_ID_ADD_NETWORK = Menu.FIRST + 3;
91    private static final int MENU_ID_ADVANCED = Menu.FIRST + 4;
92    private static final int MENU_ID_SCAN = Menu.FIRST + 5;
93    private static final int MENU_ID_CONNECT = Menu.FIRST + 6;
94    private static final int MENU_ID_FORGET = Menu.FIRST + 7;
95    private static final int MENU_ID_MODIFY = Menu.FIRST + 8;
96    private static final int MENU_ID_WRITE_NFC = Menu.FIRST + 9;
97
98    public static final int WIFI_DIALOG_ID = 1;
99    /* package */ static final int WPS_PBC_DIALOG_ID = 2;
100    private static final int WPS_PIN_DIALOG_ID = 3;
101    private static final int WRITE_NFC_DIALOG_ID = 6;
102
103    // Combo scans can take 5-6s to complete - set to 10s.
104    private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000;
105
106    // Instance state keys
107    private static final String SAVE_DIALOG_EDIT_MODE = "edit_mode";
108    private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
109
110    private static boolean savedNetworksExist;
111
112    private final IntentFilter mFilter;
113    private final BroadcastReceiver mReceiver;
114    private final Scanner mScanner;
115
116    /* package */ WifiManager mWifiManager;
117    private WifiManager.ActionListener mConnectListener;
118    private WifiManager.ActionListener mSaveListener;
119    private WifiManager.ActionListener mForgetListener;
120
121    private WifiEnabler mWifiEnabler;
122    // An access point being editted is stored here.
123    private AccessPoint mSelectedAccessPoint;
124
125    private NetworkInfo mLastNetworkInfo;
126    private WifiInfo mLastInfo;
127
128    private final AtomicBoolean mConnected = new AtomicBoolean(false);
129
130    private WifiDialog mDialog;
131    private WriteWifiConfigToNfcDialog mWifiToNfcDialog;
132
133    private TextView mEmptyView;
134
135    // this boolean extra specifies whether to disable the Next button when not connected. Used by
136    // account creation outside of setup wizard.
137    private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
138    // This string extra specifies a network to open the connect dialog on, so the user can enter
139    // network credentials.  This is used by quick settings for secured networks.
140    private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
141
142    // should Next button only be enabled when we have a connection?
143    private boolean mEnableNextOnConnection;
144
145    // Save the dialog details
146    private boolean mDlgEdit;
147    private AccessPoint mDlgAccessPoint;
148    private Bundle mAccessPointSavedState;
149
150    /** verbose logging flag. this flag is set thru developer debugging options
151     * and used so as to assist with in-the-field WiFi connectivity debugging  */
152    public static int mVerboseLogging = 0;
153
154    /* End of "used in Wifi Setup context" */
155
156    /** A restricted multimap for use in constructAccessPoints */
157    private static class Multimap<K,V> {
158        private final HashMap<K,List<V>> store = new HashMap<K,List<V>>();
159        /** retrieve a non-null list of values with key K */
160        List<V> getAll(K key) {
161            List<V> values = store.get(key);
162            return values != null ? values : Collections.<V>emptyList();
163        }
164
165        void put(K key, V val) {
166            List<V> curVals = store.get(key);
167            if (curVals == null) {
168                curVals = new ArrayList<V>(3);
169                store.put(key, curVals);
170            }
171            curVals.add(val);
172        }
173    }
174
175    private static class Scanner extends Handler {
176        private int mRetry = 0;
177        private WifiSettings mWifiSettings = null;
178
179        Scanner(WifiSettings wifiSettings) {
180            mWifiSettings = wifiSettings;
181        }
182
183        void resume() {
184            if (!hasMessages(0)) {
185                sendEmptyMessage(0);
186            }
187        }
188
189        void forceScan() {
190            removeMessages(0);
191            sendEmptyMessage(0);
192        }
193
194        void pause() {
195            mRetry = 0;
196            removeMessages(0);
197        }
198
199        @Override
200        public void handleMessage(Message message) {
201            if (mWifiSettings.mWifiManager.startScan()) {
202                mRetry = 0;
203            } else if (++mRetry >= 3) {
204                mRetry = 0;
205                Activity activity = mWifiSettings.getActivity();
206                if (activity != null) {
207                    Toast.makeText(activity, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show();
208                }
209                return;
210            }
211            sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS);
212        }
213    }
214
215    public WifiSettings() {
216        super(DISALLOW_CONFIG_WIFI);
217        mFilter = new IntentFilter();
218        mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
219        mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
220        mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION);
221        mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
222        mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION);
223        mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
224        mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
225        mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION);
226
227        mReceiver = new BroadcastReceiver() {
228            @Override
229            public void onReceive(Context context, Intent intent) {
230                handleEvent(intent);
231            }
232        };
233
234        mScanner = new Scanner(this);
235    }
236
237    @Override
238    public void onActivityCreated(Bundle savedInstanceState) {
239        super.onActivityCreated(savedInstanceState);
240
241        mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
242
243        mConnectListener = new WifiManager.ActionListener() {
244                                   @Override
245                                   public void onSuccess() {
246                                   }
247                                   @Override
248                                   public void onFailure(int reason) {
249                                       Activity activity = getActivity();
250                                       if (activity != null) {
251                                           Toast.makeText(activity,
252                                                R.string.wifi_failed_connect_message,
253                                                Toast.LENGTH_SHORT).show();
254                                       }
255                                   }
256                               };
257
258        mSaveListener = new WifiManager.ActionListener() {
259                                @Override
260                                public void onSuccess() {
261                                }
262                                @Override
263                                public void onFailure(int reason) {
264                                    Activity activity = getActivity();
265                                    if (activity != null) {
266                                        Toast.makeText(activity,
267                                            R.string.wifi_failed_save_message,
268                                            Toast.LENGTH_SHORT).show();
269                                    }
270                                }
271                            };
272
273        mForgetListener = new WifiManager.ActionListener() {
274                                   @Override
275                                   public void onSuccess() {
276                                   }
277                                   @Override
278                                   public void onFailure(int reason) {
279                                       Activity activity = getActivity();
280                                       if (activity != null) {
281                                           Toast.makeText(activity,
282                                               R.string.wifi_failed_forget_message,
283                                               Toast.LENGTH_SHORT).show();
284                                       }
285                                   }
286                               };
287
288        if (savedInstanceState != null) {
289            mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE);
290            if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
291                mAccessPointSavedState =
292                    savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
293            }
294        }
295
296        // if we're supposed to enable/disable the Next button based on our current connection
297        // state, start it off in the right state
298        Intent intent = getActivity().getIntent();
299        mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
300
301        if (mEnableNextOnConnection) {
302            if (hasNextButton()) {
303                final ConnectivityManager connectivity = (ConnectivityManager)
304                        getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
305                if (connectivity != null) {
306                    NetworkInfo info = connectivity.getNetworkInfo(
307                            ConnectivityManager.TYPE_WIFI);
308                    changeNextButtonState(info.isConnected());
309                }
310            }
311        }
312
313        addPreferencesFromResource(R.xml.wifi_settings);
314
315        mEmptyView = initEmptyView();
316        registerForContextMenu(getListView());
317        setHasOptionsMenu(true);
318
319        if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) {
320            String ssid = intent.getStringExtra(EXTRA_START_CONNECT_SSID);
321            updateAccessPoints();
322            PreferenceScreen preferenceScreen = getPreferenceScreen();
323            for (int i = 0; i < preferenceScreen.getPreferenceCount(); i++) {
324                Preference preference = preferenceScreen.getPreference(i);
325                if (preference instanceof AccessPoint) {
326                    AccessPoint accessPoint = (AccessPoint) preference;
327                    if (ssid.equals(accessPoint.ssid) && accessPoint.networkId == -1
328                            && accessPoint.security != AccessPoint.SECURITY_NONE) {
329                        onPreferenceTreeClick(preferenceScreen, preference);
330                        break;
331                    }
332                }
333            }
334        }
335    }
336
337    @Override
338    public void onDestroyView() {
339        super.onDestroyView();
340
341        if (mWifiEnabler != null) {
342            mWifiEnabler.teardownSwitchBar();
343        }
344    }
345
346    @Override
347    public void onStart() {
348        super.onStart();
349
350        // On/off switch is hidden for Setup Wizard (returns null)
351        mWifiEnabler = createWifiEnabler();
352    }
353
354    /**
355     * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard)
356     */
357    /* package */ WifiEnabler createWifiEnabler() {
358        final SettingsActivity activity = (SettingsActivity) getActivity();
359        return new WifiEnabler(activity, activity.getSwitchBar());
360    }
361
362    @Override
363    public void onResume() {
364        final Activity activity = getActivity();
365        super.onResume();
366        if (mWifiEnabler != null) {
367            mWifiEnabler.resume(activity);
368        }
369
370        activity.registerReceiver(mReceiver, mFilter);
371        updateAccessPoints();
372    }
373
374    @Override
375    public void onPause() {
376        super.onPause();
377        if (mWifiEnabler != null) {
378            mWifiEnabler.pause();
379        }
380
381        getActivity().unregisterReceiver(mReceiver);
382        mScanner.pause();
383    }
384
385    @Override
386    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
387        // If the user is not allowed to configure wifi, do not show the menu.
388        if (isUiRestricted()) return;
389
390        addOptionsMenuItems(menu);
391        super.onCreateOptionsMenu(menu, inflater);
392    }
393
394    /**
395     * @param menu
396     */
397    void addOptionsMenuItems(Menu menu) {
398        final boolean wifiIsEnabled = mWifiManager.isWifiEnabled();
399        TypedArray ta = getActivity().getTheme().obtainStyledAttributes(
400                new int[] {R.attr.ic_menu_add, R.attr.ic_wps});
401        menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network)
402                .setIcon(ta.getDrawable(0))
403                .setEnabled(wifiIsEnabled)
404                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
405        if (savedNetworksExist) {
406            menu.add(Menu.NONE, MENU_ID_SAVED_NETWORK, 0, R.string.wifi_saved_access_points_label)
407                    .setIcon(ta.getDrawable(0))
408                    .setEnabled(wifiIsEnabled)
409                    .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
410        }
411        menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.menu_stats_refresh)
412               .setEnabled(wifiIsEnabled)
413               .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
414        menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
415                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
416        ta.recycle();
417    }
418
419    @Override
420    public void onSaveInstanceState(Bundle outState) {
421        super.onSaveInstanceState(outState);
422
423        // If the dialog is showing, save its state.
424        if (mDialog != null && mDialog.isShowing()) {
425            outState.putBoolean(SAVE_DIALOG_EDIT_MODE, mDlgEdit);
426            if (mDlgAccessPoint != null) {
427                mAccessPointSavedState = new Bundle();
428                mDlgAccessPoint.saveWifiState(mAccessPointSavedState);
429                outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
430            }
431        }
432    }
433
434    @Override
435    public boolean onOptionsItemSelected(MenuItem item) {
436        // If the user is not allowed to configure wifi, do not handle menu selections.
437        if (isUiRestricted()) return false;
438
439        switch (item.getItemId()) {
440            case MENU_ID_WPS_PBC:
441                showDialog(WPS_PBC_DIALOG_ID);
442                return true;
443                /*
444            case MENU_ID_P2P:
445                if (getActivity() instanceof SettingsActivity) {
446                    ((SettingsActivity) getActivity()).startPreferencePanel(
447                            WifiP2pSettings.class.getCanonicalName(),
448                            null,
449                            R.string.wifi_p2p_settings_title, null,
450                            this, 0);
451                } else {
452                    startFragment(this, WifiP2pSettings.class.getCanonicalName(),
453                            R.string.wifi_p2p_settings_title, -1, null);
454                }
455                return true;
456                */
457            case MENU_ID_WPS_PIN:
458                showDialog(WPS_PIN_DIALOG_ID);
459                return true;
460            case MENU_ID_SCAN:
461                if (mWifiManager.isWifiEnabled()) {
462                    mScanner.forceScan();
463                }
464                return true;
465            case MENU_ID_ADD_NETWORK:
466                if (mWifiManager.isWifiEnabled()) {
467                    onAddNetworkPressed();
468                }
469                return true;
470            case MENU_ID_SAVED_NETWORK:
471                if (getActivity() instanceof SettingsActivity) {
472                    ((SettingsActivity) getActivity()).startPreferencePanel(
473                            SavedAccessPointsWifiSettings.class.getCanonicalName(), null,
474                            R.string.wifi_saved_access_points_titlebar, null, this, 0);
475                } else {
476                    startFragment(this, SavedAccessPointsWifiSettings.class.getCanonicalName(),
477                            R.string.wifi_saved_access_points_titlebar,
478                            -1 /* Do not request a result */, null);
479                }
480                return true;
481            case MENU_ID_ADVANCED:
482                if (getActivity() instanceof SettingsActivity) {
483                    ((SettingsActivity) getActivity()).startPreferencePanel(
484                            AdvancedWifiSettings.class.getCanonicalName(), null,
485                            R.string.wifi_advanced_titlebar, null, this, 0);
486                } else {
487                    startFragment(this, AdvancedWifiSettings.class.getCanonicalName(),
488                            R.string.wifi_advanced_titlebar, -1 /* Do not request a results */,
489                            null);
490                }
491                return true;
492        }
493        return super.onOptionsItemSelected(item);
494    }
495
496    @Override
497    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
498        if (info instanceof AdapterContextMenuInfo) {
499            Preference preference = (Preference) getListView().getItemAtPosition(
500                    ((AdapterContextMenuInfo) info).position);
501
502            if (preference instanceof AccessPoint) {
503                mSelectedAccessPoint = (AccessPoint) preference;
504                menu.setHeaderTitle(mSelectedAccessPoint.ssid);
505                if (mSelectedAccessPoint.getLevel() != -1) {
506                    if (mSelectedAccessPoint.getState() == null) {
507                        menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
508                    }
509                }
510
511                if (ActivityManager.getCurrentUser() == UserHandle.USER_OWNER &&
512                        (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID ||
513                        (mSelectedAccessPoint.getNetworkInfo() != null &&
514                        mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED))) {
515                    // Allow forgetting a network if the current user is the owner and either the
516                    // network is saved or ephemerally connected. (In the latter case, "forget"
517                    // blacklists the network so it won't be used again, ephemerally).
518                    menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
519                }
520                if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
521                    menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
522
523                    NfcAdapter nfcAdapter = NfcAdapter.getNfcAdapter(getActivity());
524                    if (nfcAdapter != null && nfcAdapter.isEnabled() &&
525                            mSelectedAccessPoint.security != AccessPoint.SECURITY_NONE) {
526                        // Only allow writing of NFC tags for password-protected networks.
527                        menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc);
528                    }
529                }
530            }
531        }
532    }
533
534    @Override
535    public boolean onContextItemSelected(MenuItem item) {
536        if (mSelectedAccessPoint == null) {
537            return super.onContextItemSelected(item);
538        }
539        switch (item.getItemId()) {
540            case MENU_ID_CONNECT: {
541                if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
542                    connect(mSelectedAccessPoint.networkId);
543                } else if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE) {
544                    /** Bypass dialog for unsecured networks */
545                    mSelectedAccessPoint.generateOpenNetworkConfig();
546                    connect(mSelectedAccessPoint.getConfig());
547                } else {
548                    showDialog(mSelectedAccessPoint, true);
549                }
550                return true;
551            }
552            case MENU_ID_FORGET: {
553                forget();
554                return true;
555            }
556            case MENU_ID_MODIFY: {
557                showDialog(mSelectedAccessPoint, true);
558                return true;
559            }
560            case MENU_ID_WRITE_NFC:
561                showDialog(WRITE_NFC_DIALOG_ID);
562                return true;
563
564        }
565        return super.onContextItemSelected(item);
566    }
567
568    @Override
569    public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
570        if (preference instanceof AccessPoint) {
571            mSelectedAccessPoint = (AccessPoint) preference;
572            /** Bypass dialog for unsecured, unsaved, and inactive networks */
573            if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE &&
574                    mSelectedAccessPoint.networkId == INVALID_NETWORK_ID &&
575                    !mSelectedAccessPoint.isActive()) {
576                mSelectedAccessPoint.generateOpenNetworkConfig();
577                if (!savedNetworksExist) {
578                    savedNetworksExist = true;
579                    getActivity().invalidateOptionsMenu();
580                }
581                connect(mSelectedAccessPoint.getConfig());
582            } else {
583                showDialog(mSelectedAccessPoint, false);
584            }
585        } else {
586            return super.onPreferenceTreeClick(screen, preference);
587        }
588        return true;
589    }
590
591    private void showDialog(AccessPoint accessPoint, boolean edit) {
592        if (mDialog != null) {
593            removeDialog(WIFI_DIALOG_ID);
594            mDialog = null;
595        }
596
597        // Save the access point and edit mode
598        mDlgAccessPoint = accessPoint;
599        mDlgEdit = edit;
600
601        showDialog(WIFI_DIALOG_ID);
602    }
603
604    @Override
605    public Dialog onCreateDialog(int dialogId) {
606        switch (dialogId) {
607            case WIFI_DIALOG_ID:
608                AccessPoint ap = mDlgAccessPoint; // For manual launch
609                if (ap == null) { // For re-launch from saved state
610                    if (mAccessPointSavedState != null) {
611                        ap = new AccessPoint(getActivity(), mAccessPointSavedState);
612                        // For repeated orientation changes
613                        mDlgAccessPoint = ap;
614                        // Reset the saved access point data
615                        mAccessPointSavedState = null;
616                    }
617                }
618                // If it's null, fine, it's for Add Network
619                mSelectedAccessPoint = ap;
620                mDialog = new WifiDialog(getActivity(), this, ap, mDlgEdit);
621                return mDialog;
622            case WPS_PBC_DIALOG_ID:
623                return new WpsDialog(getActivity(), WpsInfo.PBC);
624            case WPS_PIN_DIALOG_ID:
625                return new WpsDialog(getActivity(), WpsInfo.DISPLAY);
626            case WRITE_NFC_DIALOG_ID:
627                if (mSelectedAccessPoint != null) {
628                    mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
629                            getActivity(), mSelectedAccessPoint, mWifiManager);
630                    return mWifiToNfcDialog;
631                }
632
633        }
634        return super.onCreateDialog(dialogId);
635    }
636
637    /**
638     * Shows the latest access points available with supplemental information like
639     * the strength of network and the security for it.
640     */
641    private void updateAccessPoints() {
642        // Safeguard from some delayed event handling
643        if (getActivity() == null) return;
644
645        if (isUiRestricted()) {
646            addMessagePreference(R.string.wifi_empty_list_user_restricted);
647            return;
648        }
649        final int wifiState = mWifiManager.getWifiState();
650
651        //when we update the screen, check if verbose logging has been turned on or off
652        mVerboseLogging = mWifiManager.getVerboseLoggingLevel();
653
654        switch (wifiState) {
655            case WifiManager.WIFI_STATE_ENABLED:
656                // AccessPoints are automatically sorted with TreeSet.
657                final Collection<AccessPoint> accessPoints =
658                        constructAccessPoints(getActivity(), mWifiManager, mLastInfo,
659                                mLastNetworkInfo);
660                getPreferenceScreen().removeAll();
661                if (accessPoints.size() == 0) {
662                    addMessagePreference(R.string.wifi_empty_list_wifi_on);
663                }
664
665                for (AccessPoint accessPoint : accessPoints) {
666                    // Ignore access points that are out of range.
667                    if (accessPoint.getLevel() != -1) {
668                        getPreferenceScreen().addPreference(accessPoint);
669                    }
670                }
671                break;
672
673            case WifiManager.WIFI_STATE_ENABLING:
674                getPreferenceScreen().removeAll();
675                break;
676
677            case WifiManager.WIFI_STATE_DISABLING:
678                addMessagePreference(R.string.wifi_stopping);
679                break;
680
681            case WifiManager.WIFI_STATE_DISABLED:
682                setOffMessage();
683                break;
684        }
685    }
686
687    protected TextView initEmptyView() {
688        TextView emptyView = (TextView) getActivity().findViewById(android.R.id.empty);
689        getListView().setEmptyView(emptyView);
690        return emptyView;
691    }
692
693    private void setOffMessage() {
694        if (mEmptyView != null) {
695            mEmptyView.setText(R.string.wifi_empty_list_wifi_off);
696            if (android.provider.Settings.Global.getInt(getActivity().getContentResolver(),
697                    android.provider.Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1) {
698                mEmptyView.append("\n\n");
699                int resId;
700                if (android.provider.Settings.Secure.isLocationProviderEnabled(
701                        getActivity().getContentResolver(), LocationManager.NETWORK_PROVIDER)) {
702                    resId = R.string.wifi_scan_notify_text_location_on;
703                } else {
704                    resId = R.string.wifi_scan_notify_text_location_off;
705                }
706                CharSequence charSeq = getText(resId);
707                mEmptyView.append(charSeq);
708            }
709        }
710        getPreferenceScreen().removeAll();
711    }
712
713    private void addMessagePreference(int messageId) {
714        if (mEmptyView != null) mEmptyView.setText(messageId);
715        getPreferenceScreen().removeAll();
716    }
717
718    /** Returns sorted list of access points */
719    private static List<AccessPoint> constructAccessPoints(Context context,
720            WifiManager wifiManager, WifiInfo lastInfo, NetworkInfo lastNetworkInfo) {
721        ArrayList<AccessPoint> accessPoints = new ArrayList<AccessPoint>();
722        /** Lookup table to more quickly update AccessPoints by only considering objects with the
723         * correct SSID.  Maps SSID -> List of AccessPoints with the given SSID.  */
724        Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>();
725
726        final List<WifiConfiguration> configs = wifiManager.getConfiguredNetworks();
727        if (configs != null) {
728            // Update "Saved Networks" menu option.
729            if (savedNetworksExist != (configs.size() > 0)) {
730                savedNetworksExist = !savedNetworksExist;
731                if (context instanceof Activity) {
732                    ((Activity) context).invalidateOptionsMenu();
733                }
734            }
735            for (WifiConfiguration config : configs) {
736                if (config.selfAdded && config.numAssociation == 0) {
737                    continue;
738                }
739                AccessPoint accessPoint = new AccessPoint(context, config);
740                if (lastInfo != null && lastNetworkInfo != null) {
741                    accessPoint.update(lastInfo, lastNetworkInfo);
742                }
743                accessPoints.add(accessPoint);
744                apMap.put(accessPoint.ssid, accessPoint);
745            }
746        }
747
748        final List<ScanResult> results = wifiManager.getScanResults();
749        if (results != null) {
750            for (ScanResult result : results) {
751                // Ignore hidden and ad-hoc networks.
752                if (result.SSID == null || result.SSID.length() == 0 ||
753                        result.capabilities.contains("[IBSS]")) {
754                    continue;
755                }
756
757                boolean found = false;
758                for (AccessPoint accessPoint : apMap.getAll(result.SSID)) {
759                    if (accessPoint.update(result))
760                        found = true;
761                }
762                if (!found) {
763                    AccessPoint accessPoint = new AccessPoint(context, result);
764                    if (lastInfo != null && lastNetworkInfo != null) {
765                        accessPoint.update(lastInfo, lastNetworkInfo);
766                    }
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    private void handleEvent(Intent intent) {
779        String action = intent.getAction();
780        if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) {
781            updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE,
782                    WifiManager.WIFI_STATE_UNKNOWN));
783        } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) ||
784                WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) ||
785                WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) {
786                updateAccessPoints();
787        } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) {
788            NetworkInfo info = (NetworkInfo) intent.getParcelableExtra(
789                    WifiManager.EXTRA_NETWORK_INFO);
790            mConnected.set(info.isConnected());
791            changeNextButtonState(info.isConnected());
792            updateAccessPoints();
793            updateNetworkInfo(info);
794        } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) {
795            updateNetworkInfo(null);
796        }
797    }
798
799    private void updateNetworkInfo(NetworkInfo networkInfo) {
800        /* sticky broadcasts can call this when wifi is disabled */
801        if (!mWifiManager.isWifiEnabled()) {
802            mScanner.pause();
803            return;
804        }
805
806        if (networkInfo != null &&
807                networkInfo.getDetailedState() == DetailedState.OBTAINING_IPADDR) {
808            mScanner.pause();
809        } else {
810            mScanner.resume();
811        }
812
813        mLastInfo = mWifiManager.getConnectionInfo();
814        if (networkInfo != null) {
815            mLastNetworkInfo = networkInfo;
816        }
817
818        for (int i = getPreferenceScreen().getPreferenceCount() - 1; i >= 0; --i) {
819            // Maybe there's a WifiConfigPreference
820            Preference preference = getPreferenceScreen().getPreference(i);
821            if (preference instanceof AccessPoint) {
822                final AccessPoint accessPoint = (AccessPoint) preference;
823                accessPoint.update(mLastInfo, mLastNetworkInfo);
824            }
825        }
826    }
827
828    private void updateWifiState(int state) {
829        Activity activity = getActivity();
830        if (activity != null) {
831            activity.invalidateOptionsMenu();
832        }
833
834        switch (state) {
835            case WifiManager.WIFI_STATE_ENABLED:
836                mScanner.resume();
837                return; // not break, to avoid the call to pause() below
838
839            case WifiManager.WIFI_STATE_ENABLING:
840                addMessagePreference(R.string.wifi_starting);
841                break;
842
843            case WifiManager.WIFI_STATE_DISABLED:
844                setOffMessage();
845                break;
846        }
847
848        mLastInfo = null;
849        mLastNetworkInfo = null;
850        mScanner.pause();
851    }
852
853    /**
854     * Renames/replaces "Next" button when appropriate. "Next" button usually exists in
855     * Wifi setup screens, not in usual wifi settings screen.
856     *
857     * @param enabled true when the device is connected to a wifi network.
858     */
859    private void changeNextButtonState(boolean enabled) {
860        if (mEnableNextOnConnection && hasNextButton()) {
861            getNextButton().setEnabled(enabled);
862        }
863    }
864
865    @Override
866    public void onClick(DialogInterface dialogInterface, int button) {
867        if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) {
868            forget();
869        } else if (button == WifiDialog.BUTTON_SUBMIT) {
870            if (mDialog != null) {
871                submit(mDialog.getController());
872            }
873        }
874    }
875
876    /* package */ void submit(WifiConfigController configController) {
877
878        final WifiConfiguration config = configController.getConfig();
879
880        if (config == null) {
881            if (mSelectedAccessPoint != null
882                    && mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) {
883                connect(mSelectedAccessPoint.networkId);
884            }
885        } else if (config.networkId != INVALID_NETWORK_ID) {
886            if (mSelectedAccessPoint != null) {
887                mWifiManager.save(config, mSaveListener);
888            }
889        } else {
890            if (configController.isEdit()) {
891                mWifiManager.save(config, mSaveListener);
892            } else {
893                connect(config);
894            }
895        }
896
897        if (mWifiManager.isWifiEnabled()) {
898            mScanner.resume();
899        }
900        updateAccessPoints();
901    }
902
903    /* package */ void forget() {
904        if (mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) {
905            if (mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED) {
906                // Network is active but has no network ID - must be ephemeral.
907                mWifiManager.disableEphemeralNetwork(
908                        AccessPoint.convertToQuotedString(mSelectedAccessPoint.ssid));
909            } else {
910                // Should not happen, but a monkey seems to trigger it
911                Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig());
912                return;
913            }
914        } else {
915            mWifiManager.forget(mSelectedAccessPoint.networkId, mForgetListener);
916        }
917
918
919        if (mWifiManager.isWifiEnabled()) {
920            mScanner.resume();
921        }
922        updateAccessPoints();
923
924        // We need to rename/replace "Next" button in wifi setup context.
925        changeNextButtonState(false);
926    }
927
928    protected void connect(final WifiConfiguration config) {
929        mWifiManager.connect(config, mConnectListener);
930    }
931
932    protected void connect(final int networkId) {
933        mWifiManager.connect(networkId, mConnectListener);
934    }
935
936    /**
937     * Refreshes acccess points and ask Wifi module to scan networks again.
938     */
939    /* package */ void refreshAccessPoints() {
940        if (mWifiManager.isWifiEnabled()) {
941            mScanner.resume();
942        }
943
944        getPreferenceScreen().removeAll();
945    }
946
947    /**
948     * Called when "add network" button is pressed.
949     */
950    /* package */ void onAddNetworkPressed() {
951        // No exact access point is selected.
952        mSelectedAccessPoint = null;
953        showDialog(null, true);
954    }
955
956    /* package */ int getAccessPointsCount() {
957        final boolean wifiIsEnabled = mWifiManager.isWifiEnabled();
958        if (wifiIsEnabled) {
959            return getPreferenceScreen().getPreferenceCount();
960        } else {
961            return 0;
962        }
963    }
964
965    /**
966     * Requests wifi module to pause wifi scan. May be ignored when the module is disabled.
967     */
968    /* package */ void pauseWifiScan() {
969        if (mWifiManager.isWifiEnabled()) {
970            mScanner.pause();
971        }
972    }
973
974    /**
975     * Requests wifi module to resume wifi scan. May be ignored when the module is disabled.
976     */
977    /* package */ void resumeWifiScan() {
978        if (mWifiManager.isWifiEnabled()) {
979            mScanner.resume();
980        }
981    }
982
983    @Override
984    protected int getHelpResource() {
985        return R.string.help_url_wifi;
986    }
987
988    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
989        new BaseSearchIndexProvider() {
990            @Override
991            public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
992                final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
993                final Resources res = context.getResources();
994
995                // Add fragment title
996                SearchIndexableRaw data = new SearchIndexableRaw(context);
997                data.title = res.getString(R.string.wifi_settings);
998                data.screenTitle = res.getString(R.string.wifi_settings);
999                data.keywords = res.getString(R.string.keywords_wifi);
1000                result.add(data);
1001
1002                // Add available Wi-Fi access points
1003                WifiManager wifiManager =
1004                        (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
1005                final Collection<AccessPoint> accessPoints =
1006                        constructAccessPoints(context, wifiManager, null, null);
1007                for (AccessPoint accessPoint : accessPoints) {
1008                    // We are indexing only the saved Wi-Fi networks.
1009                    if (accessPoint.getConfig() == null) continue;
1010                    data = new SearchIndexableRaw(context);
1011                    data.title = accessPoint.getTitle().toString();
1012                    data.screenTitle = res.getString(R.string.wifi_settings);
1013                    data.enabled = enabled;
1014                    result.add(data);
1015                }
1016
1017                return result;
1018            }
1019        };
1020}
1021