WifiSettings.java revision cb5444ad2ccf2f16bddafb8844cde6a1a92adddb
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.AlertDialog;
24import android.app.AppGlobals;
25import android.app.Dialog;
26import android.app.admin.DevicePolicyManager;
27import android.content.Context;
28import android.content.DialogInterface;
29import android.content.Intent;
30import android.content.pm.ApplicationInfo;
31import android.content.pm.IPackageManager;
32import android.content.pm.PackageManager;
33import android.content.pm.PackageManager.NameNotFoundException;
34import android.content.res.Resources;
35import android.content.res.TypedArray;
36import android.net.ConnectivityManager;
37import android.net.NetworkInfo;
38import android.net.NetworkInfo.State;
39import android.net.wifi.WifiConfiguration;
40import android.net.wifi.WifiManager;
41import android.net.wifi.WpsInfo;
42import android.nfc.NfcAdapter;
43import android.os.Bundle;
44import android.os.RemoteException;
45import android.os.UserHandle;
46import android.os.UserManager;
47import android.preference.Preference;
48import android.preference.PreferenceScreen;
49import android.text.Spannable;
50import android.text.style.TextAppearanceSpan;
51import android.util.Log;
52import android.view.ContextMenu;
53import android.view.ContextMenu.ContextMenuInfo;
54import android.view.Gravity;
55import android.view.Menu;
56import android.view.MenuInflater;
57import android.view.MenuItem;
58import android.view.View;
59import android.widget.AdapterView.AdapterContextMenuInfo;
60import android.widget.ProgressBar;
61import android.widget.TextView;
62import android.widget.TextView.BufferType;
63import android.widget.Toast;
64
65import com.android.internal.logging.MetricsLogger;
66import com.android.settings.LinkifyUtils;
67import com.android.settings.R;
68import com.android.settings.RestrictedSettingsFragment;
69import com.android.settings.SettingsActivity;
70import com.android.settings.location.ScanningSettings;
71import com.android.settings.search.BaseSearchIndexProvider;
72import com.android.settings.search.Indexable;
73import com.android.settings.search.SearchIndexableRaw;
74import com.android.settingslib.wifi.AccessPoint;
75import com.android.settingslib.wifi.AccessPoint.AccessPointListener;
76import com.android.settingslib.wifi.WifiTracker;
77
78import java.util.ArrayList;
79import java.util.Collection;
80import java.util.List;
81
82/**
83 * Two types of UI are provided here.
84 *
85 * The first is for "usual Settings", appearing as any other Setup fragment.
86 *
87 * The second is for Setup Wizard, with a simplified interface that hides the action bar
88 * and menus.
89 */
90public class WifiSettings extends RestrictedSettingsFragment
91        implements DialogInterface.OnClickListener, Indexable, WifiTracker.WifiListener,
92        AccessPointListener {
93
94    private static final String TAG = "WifiSettings";
95
96    /* package */ 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_SAVED_NETWORK = Menu.FIRST + 2;
99    /* package */ 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    private static final int MENU_ID_WRITE_NFC = Menu.FIRST + 9;
106
107    public static final int WIFI_DIALOG_ID = 1;
108    /* package */ static final int WPS_PBC_DIALOG_ID = 2;
109    private static final int WPS_PIN_DIALOG_ID = 3;
110    private static final int WRITE_NFC_DIALOG_ID = 6;
111
112    // Instance state keys
113    private static final String SAVE_DIALOG_EDIT_MODE = "edit_mode";
114    private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
115    private static final String SAVED_WIFI_NFC_DIALOG_STATE = "wifi_nfc_dlg_state";
116
117    private static boolean savedNetworksExist;
118
119    protected WifiManager mWifiManager;
120    private WifiManager.ActionListener mConnectListener;
121    private WifiManager.ActionListener mSaveListener;
122    private WifiManager.ActionListener mForgetListener;
123
124    private WifiEnabler mWifiEnabler;
125    // An access point being editted is stored here.
126    private AccessPoint mSelectedAccessPoint;
127
128    private WifiDialog mDialog;
129    private WriteWifiConfigToNfcDialog mWifiToNfcDialog;
130
131    private TextView mEmptyView;
132    private ProgressBar mProgressHeader;
133
134    // this boolean extra specifies whether to disable the Next button when not connected. Used by
135    // account creation outside of setup wizard.
136    private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
137    // This string extra specifies a network to open the connect dialog on, so the user can enter
138    // network credentials.  This is used by quick settings for secured networks.
139    private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
140
141    // should Next button only be enabled when we have a connection?
142    private boolean mEnableNextOnConnection;
143
144    // Save the dialog details
145    private boolean mDlgEdit;
146    private AccessPoint mDlgAccessPoint;
147    private Bundle mAccessPointSavedState;
148    private Bundle mWifiNfcDialogSavedState;
149
150    private WifiTracker mWifiTracker;
151    private String mOpenSsid;
152
153    /* End of "used in Wifi Setup context" */
154
155    public WifiSettings() {
156        super(DISALLOW_CONFIG_WIFI);
157    }
158
159    @Override
160    public void onViewCreated(View view, Bundle savedInstanceState) {
161        super.onViewCreated(view, savedInstanceState);
162        final Activity activity = getActivity();
163        if (activity != null) {
164            mProgressHeader = (ProgressBar) setPinnedHeaderView(R.layout.wifi_progress_header);
165        }
166    }
167
168    @Override
169    public void onActivityCreated(Bundle savedInstanceState) {
170        super.onActivityCreated(savedInstanceState);
171
172        mWifiTracker = new WifiTracker(getActivity(), this, true, true, false);
173        mWifiManager = mWifiTracker.getManager();
174
175        mConnectListener = new WifiManager.ActionListener() {
176                                   @Override
177                                   public void onSuccess() {
178                                   }
179                                   @Override
180                                   public void onFailure(int reason) {
181                                       Activity activity = getActivity();
182                                       if (activity != null) {
183                                           Toast.makeText(activity,
184                                                R.string.wifi_failed_connect_message,
185                                                Toast.LENGTH_SHORT).show();
186                                       }
187                                   }
188                               };
189
190        mSaveListener = new WifiManager.ActionListener() {
191                                @Override
192                                public void onSuccess() {
193                                }
194                                @Override
195                                public void onFailure(int reason) {
196                                    Activity activity = getActivity();
197                                    if (activity != null) {
198                                        Toast.makeText(activity,
199                                            R.string.wifi_failed_save_message,
200                                            Toast.LENGTH_SHORT).show();
201                                    }
202                                }
203                            };
204
205        mForgetListener = new WifiManager.ActionListener() {
206                                   @Override
207                                   public void onSuccess() {
208                                   }
209                                   @Override
210                                   public void onFailure(int reason) {
211                                       Activity activity = getActivity();
212                                       if (activity != null) {
213                                           Toast.makeText(activity,
214                                               R.string.wifi_failed_forget_message,
215                                               Toast.LENGTH_SHORT).show();
216                                       }
217                                   }
218                               };
219
220        if (savedInstanceState != null) {
221            mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE);
222            if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
223                mAccessPointSavedState =
224                    savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
225            }
226
227            if (savedInstanceState.containsKey(SAVED_WIFI_NFC_DIALOG_STATE)) {
228                mWifiNfcDialogSavedState =
229                    savedInstanceState.getBundle(SAVED_WIFI_NFC_DIALOG_STATE);
230            }
231        }
232
233        // if we're supposed to enable/disable the Next button based on our current connection
234        // state, start it off in the right state
235        Intent intent = getActivity().getIntent();
236        mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
237
238        if (mEnableNextOnConnection) {
239            if (hasNextButton()) {
240                final ConnectivityManager connectivity = (ConnectivityManager)
241                        getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
242                if (connectivity != null) {
243                    NetworkInfo info = connectivity.getNetworkInfo(
244                            ConnectivityManager.TYPE_WIFI);
245                    changeNextButtonState(info.isConnected());
246                }
247            }
248        }
249
250        addPreferencesFromResource(R.xml.wifi_settings);
251
252        mEmptyView = initEmptyView();
253        registerForContextMenu(getListView());
254        setHasOptionsMenu(true);
255
256        if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) {
257            mOpenSsid = intent.getStringExtra(EXTRA_START_CONNECT_SSID);
258            onAccessPointsChanged();
259        }
260    }
261
262    @Override
263    public void onDestroyView() {
264        super.onDestroyView();
265
266        if (mWifiEnabler != null) {
267            mWifiEnabler.teardownSwitchBar();
268        }
269    }
270
271    @Override
272    public void onStart() {
273        super.onStart();
274
275        // On/off switch is hidden for Setup Wizard (returns null)
276        mWifiEnabler = createWifiEnabler();
277    }
278
279    /**
280     * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard)
281     */
282    /* package */ WifiEnabler createWifiEnabler() {
283        final SettingsActivity activity = (SettingsActivity) getActivity();
284        return new WifiEnabler(activity, activity.getSwitchBar());
285    }
286
287    @Override
288    public void onResume() {
289        final Activity activity = getActivity();
290        super.onResume();
291        if (mWifiEnabler != null) {
292            mWifiEnabler.resume(activity);
293        }
294
295        mWifiTracker.startTracking();
296    }
297
298    @Override
299    public void onPause() {
300        super.onPause();
301        if (mWifiEnabler != null) {
302            mWifiEnabler.pause();
303        }
304
305        mWifiTracker.stopTracking();
306    }
307
308    @Override
309    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
310        // If the user is not allowed to configure wifi, do not show the menu.
311        if (isUiRestricted()) return;
312
313        addOptionsMenuItems(menu);
314        super.onCreateOptionsMenu(menu, inflater);
315    }
316
317    /**
318     * @param menu
319     */
320    void addOptionsMenuItems(Menu menu) {
321        final boolean wifiIsEnabled = mWifiTracker.isWifiEnabled();
322        TypedArray ta = getActivity().getTheme().obtainStyledAttributes(
323                new int[] {R.attr.ic_menu_add, R.attr.ic_wps});
324        menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network)
325                .setIcon(ta.getDrawable(0))
326                .setEnabled(wifiIsEnabled)
327                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
328        if (savedNetworksExist) {
329            menu.add(Menu.NONE, MENU_ID_SAVED_NETWORK, 0, R.string.wifi_saved_access_points_label)
330                    .setIcon(ta.getDrawable(0))
331                    .setEnabled(wifiIsEnabled)
332                    .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
333        }
334        menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.menu_stats_refresh)
335               .setEnabled(wifiIsEnabled)
336               .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
337        menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
338                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
339        ta.recycle();
340    }
341
342    @Override
343    protected int getMetricsCategory() {
344        return MetricsLogger.WIFI;
345    }
346
347    @Override
348    public void onSaveInstanceState(Bundle outState) {
349        super.onSaveInstanceState(outState);
350
351        // If the dialog is showing, save its state.
352        if (mDialog != null && mDialog.isShowing()) {
353            outState.putBoolean(SAVE_DIALOG_EDIT_MODE, mDlgEdit);
354            if (mDlgAccessPoint != null) {
355                mAccessPointSavedState = new Bundle();
356                mDlgAccessPoint.saveWifiState(mAccessPointSavedState);
357                outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
358            }
359        }
360
361        if (mWifiToNfcDialog != null && mWifiToNfcDialog.isShowing()) {
362            Bundle savedState = new Bundle();
363            mWifiToNfcDialog.saveState(savedState);
364            outState.putBundle(SAVED_WIFI_NFC_DIALOG_STATE, savedState);
365        }
366    }
367
368    @Override
369    public boolean onOptionsItemSelected(MenuItem item) {
370        // If the user is not allowed to configure wifi, do not handle menu selections.
371        if (isUiRestricted()) return false;
372
373        switch (item.getItemId()) {
374            case MENU_ID_WPS_PBC:
375                showDialog(WPS_PBC_DIALOG_ID);
376                return true;
377                /*
378            case MENU_ID_P2P:
379                if (getActivity() instanceof SettingsActivity) {
380                    ((SettingsActivity) getActivity()).startPreferencePanel(
381                            WifiP2pSettings.class.getCanonicalName(),
382                            null,
383                            R.string.wifi_p2p_settings_title, null,
384                            this, 0);
385                } else {
386                    startFragment(this, WifiP2pSettings.class.getCanonicalName(),
387                            R.string.wifi_p2p_settings_title, -1, null);
388                }
389                return true;
390                */
391            case MENU_ID_WPS_PIN:
392                showDialog(WPS_PIN_DIALOG_ID);
393                return true;
394            case MENU_ID_SCAN:
395                MetricsLogger.action(getActivity(), MetricsLogger.ACTION_WIFI_FORCE_SCAN);
396                mWifiTracker.forceScan();
397                return true;
398            case MENU_ID_ADD_NETWORK:
399                if (mWifiTracker.isWifiEnabled()) {
400                    onAddNetworkPressed();
401                }
402                return true;
403            case MENU_ID_SAVED_NETWORK:
404                if (getActivity() instanceof SettingsActivity) {
405                    ((SettingsActivity) getActivity()).startPreferencePanel(
406                            SavedAccessPointsWifiSettings.class.getCanonicalName(), null,
407                            R.string.wifi_saved_access_points_titlebar, null, this, 0);
408                } else {
409                    startFragment(this, SavedAccessPointsWifiSettings.class.getCanonicalName(),
410                            R.string.wifi_saved_access_points_titlebar,
411                            -1 /* Do not request a result */, null);
412                }
413                return true;
414            case MENU_ID_ADVANCED:
415                if (getActivity() instanceof SettingsActivity) {
416                    ((SettingsActivity) getActivity()).startPreferencePanel(
417                            AdvancedWifiSettings.class.getCanonicalName(), null,
418                            R.string.wifi_advanced_titlebar, null, this, 0);
419                } else {
420                    startFragment(this, AdvancedWifiSettings.class.getCanonicalName(),
421                            R.string.wifi_advanced_titlebar, -1 /* Do not request a results */,
422                            null);
423                }
424                return true;
425        }
426        return super.onOptionsItemSelected(item);
427    }
428
429    @Override
430    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
431        if (info instanceof AdapterContextMenuInfo) {
432            Preference preference = (Preference) getListView().getItemAtPosition(
433                    ((AdapterContextMenuInfo) info).position);
434
435            if (preference instanceof AccessPointPreference) {
436                mSelectedAccessPoint = ((AccessPointPreference) preference).getAccessPoint();
437                menu.setHeaderTitle(mSelectedAccessPoint.getSsid());
438                if (mSelectedAccessPoint.isConnectable()) {
439                    menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
440                }
441
442                WifiConfiguration config = mSelectedAccessPoint.getConfig();
443                // Some configs are ineditable
444                if (isEditabilityLockedDown(getActivity(), config)) {
445                    return;
446                }
447
448                if (mSelectedAccessPoint.isSaved() || mSelectedAccessPoint.isEphemeral()) {
449                    // Allow forgetting a network if either the network is saved or ephemerally
450                    // connected. (In the latter case, "forget" blacklists the network so it won't
451                    // be used again, ephemerally).
452                    menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
453                }
454                if (mSelectedAccessPoint.isSaved()) {
455                    menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
456                    NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
457                    if (nfcAdapter != null && nfcAdapter.isEnabled() &&
458                            mSelectedAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
459                        // Only allow writing of NFC tags for password-protected networks.
460                        menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc);
461                    }
462                }
463            }
464        }
465    }
466
467    @Override
468    public boolean onContextItemSelected(MenuItem item) {
469        if (mSelectedAccessPoint == null) {
470            return super.onContextItemSelected(item);
471        }
472        switch (item.getItemId()) {
473            case MENU_ID_CONNECT: {
474                if (mSelectedAccessPoint.isSaved()) {
475                    connect(mSelectedAccessPoint.getConfig());
476                } else if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE) {
477                    /** Bypass dialog for unsecured networks */
478                    mSelectedAccessPoint.generateOpenNetworkConfig();
479                    connect(mSelectedAccessPoint.getConfig());
480                } else {
481                    showDialog(mSelectedAccessPoint, true);
482                }
483                return true;
484            }
485            case MENU_ID_FORGET: {
486                forget();
487                return true;
488            }
489            case MENU_ID_MODIFY: {
490                showDialog(mSelectedAccessPoint, true);
491                return true;
492            }
493            case MENU_ID_WRITE_NFC:
494                showDialog(WRITE_NFC_DIALOG_ID);
495                return true;
496
497        }
498        return super.onContextItemSelected(item);
499    }
500
501    @Override
502    public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) {
503        if (preference instanceof AccessPointPreference) {
504            mSelectedAccessPoint = ((AccessPointPreference) preference).getAccessPoint();
505            /** Bypass dialog for unsecured, unsaved, and inactive networks */
506            if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE &&
507                    !mSelectedAccessPoint.isSaved() && !mSelectedAccessPoint.isActive()) {
508                mSelectedAccessPoint.generateOpenNetworkConfig();
509                if (!savedNetworksExist) {
510                    savedNetworksExist = true;
511                    getActivity().invalidateOptionsMenu();
512                }
513                connect(mSelectedAccessPoint.getConfig());
514            } else {
515                showDialog(mSelectedAccessPoint, false);
516            }
517        } else {
518            return super.onPreferenceTreeClick(screen, preference);
519        }
520        return true;
521    }
522
523    private void showDialog(AccessPoint accessPoint, boolean edit) {
524        if (accessPoint != null) {
525            WifiConfiguration config = accessPoint.getConfig();
526            if (isEditabilityLockedDown(getActivity(), config) && accessPoint.isActive()) {
527                final int userId = UserHandle.getUserId(config.creatorUid);
528                final PackageManager pm = getActivity().getPackageManager();
529                final IPackageManager ipm = AppGlobals.getPackageManager();
530                String appName = pm.getNameForUid(config.creatorUid);
531                try {
532                    final ApplicationInfo appInfo = ipm.getApplicationInfo(appName, /* flags */ 0,
533                            userId);
534                    final CharSequence label = pm.getApplicationLabel(appInfo);
535                    if (label != null) {
536                        appName = label.toString();
537                    }
538                } catch (RemoteException e) {
539                    // leave appName as packageName
540                }
541                final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
542                builder.setTitle(accessPoint.getSsid())
543                        .setMessage(getString(R.string.wifi_alert_lockdown_by_device_owner,
544                                appName))
545                        .setPositiveButton(android.R.string.ok, null)
546                        .show();
547                return;
548            }
549        }
550
551        if (mDialog != null) {
552            removeDialog(WIFI_DIALOG_ID);
553            mDialog = null;
554        }
555
556        // Save the access point and edit mode
557        mDlgAccessPoint = accessPoint;
558        mDlgEdit = edit;
559
560        showDialog(WIFI_DIALOG_ID);
561    }
562
563    @Override
564    public Dialog onCreateDialog(int dialogId) {
565        switch (dialogId) {
566            case WIFI_DIALOG_ID:
567                AccessPoint ap = mDlgAccessPoint; // For manual launch
568                if (ap == null) { // For re-launch from saved state
569                    if (mAccessPointSavedState != null) {
570                        ap = new AccessPoint(getActivity(), mAccessPointSavedState);
571                        // For repeated orientation changes
572                        mDlgAccessPoint = ap;
573                        // Reset the saved access point data
574                        mAccessPointSavedState = null;
575                    }
576                }
577                // If it's null, fine, it's for Add Network
578                mSelectedAccessPoint = ap;
579                final boolean hideForget = (ap == null || isEditabilityLockedDown(getActivity(),
580                        ap.getConfig()));
581                mDialog = new WifiDialog(getActivity(), this, ap, mDlgEdit,
582                        /* no hide submit/connect */ false,
583                        /* hide forget if config locked down */ hideForget);
584                return mDialog;
585            case WPS_PBC_DIALOG_ID:
586                return new WpsDialog(getActivity(), WpsInfo.PBC);
587            case WPS_PIN_DIALOG_ID:
588                return new WpsDialog(getActivity(), WpsInfo.DISPLAY);
589            case WRITE_NFC_DIALOG_ID:
590                if (mSelectedAccessPoint != null) {
591                    mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
592                            getActivity(), mSelectedAccessPoint.getConfig().networkId,
593                            mSelectedAccessPoint.getSecurity(),
594                            mWifiManager);
595                } else if (mWifiNfcDialogSavedState != null) {
596                    mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
597                            getActivity(), mWifiNfcDialogSavedState, mWifiManager);
598                }
599
600                return mWifiToNfcDialog;
601        }
602        return super.onCreateDialog(dialogId);
603    }
604
605    /**
606     * Shows the latest access points available with supplemental information like
607     * the strength of network and the security for it.
608     */
609    @Override
610    public void onAccessPointsChanged() {
611        // Safeguard from some delayed event handling
612        if (getActivity() == null) return;
613
614        if (isUiRestricted()) {
615            addMessagePreference(R.string.wifi_empty_list_user_restricted);
616            return;
617        }
618        final int wifiState = mWifiManager.getWifiState();
619
620        switch (wifiState) {
621            case WifiManager.WIFI_STATE_ENABLED:
622                // AccessPoints are automatically sorted with TreeSet.
623                final Collection<AccessPoint> accessPoints =
624                        mWifiTracker.getAccessPoints();
625                getPreferenceScreen().removeAll();
626
627                boolean hasAvailableAccessPoints = false;
628                for (AccessPoint accessPoint : accessPoints) {
629                    // Ignore access points that are out of range.
630                    if (accessPoint.getLevel() != -1) {
631                        hasAvailableAccessPoints = true;
632                        AccessPointPreference preference = new AccessPointPreference(accessPoint,
633                                getActivity(), false);
634
635                        if (mOpenSsid != null && mOpenSsid.equals(accessPoint.getSsid())
636                                && !accessPoint.isSaved()
637                                && accessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
638                            onPreferenceTreeClick(getPreferenceScreen(), preference);
639                            mOpenSsid = null;
640                        }
641                        getPreferenceScreen().addPreference(preference);
642                        accessPoint.setListener(this);
643                    }
644                }
645                if (!hasAvailableAccessPoints) {
646                    setProgressBarVisible(true);
647                    addMessagePreference(R.string.wifi_empty_list_wifi_on);
648                } else {
649                    setProgressBarVisible(false);
650                }
651                break;
652
653            case WifiManager.WIFI_STATE_ENABLING:
654                getPreferenceScreen().removeAll();
655                setProgressBarVisible(true);
656                break;
657
658            case WifiManager.WIFI_STATE_DISABLING:
659                addMessagePreference(R.string.wifi_stopping);
660                setProgressBarVisible(true);
661                break;
662
663            case WifiManager.WIFI_STATE_DISABLED:
664                setOffMessage();
665                setProgressBarVisible(false);
666                break;
667        }
668        // Update "Saved Networks" menu option.
669        if (savedNetworksExist != mWifiTracker.doSavedNetworksExist()) {
670            savedNetworksExist = !savedNetworksExist;
671            getActivity().invalidateOptionsMenu();
672        }
673    }
674
675    protected TextView initEmptyView() {
676        TextView emptyView = (TextView) getActivity().findViewById(android.R.id.empty);
677        emptyView.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
678        getListView().setEmptyView(emptyView);
679        return emptyView;
680    }
681
682    private void setOffMessage() {
683        if (mEmptyView == null) {
684            return;
685        }
686
687        final CharSequence briefText = getText(R.string.wifi_empty_list_wifi_off);
688        if (isUiRestricted()) {
689            // Show only the brief text if the user is not allowed to configure scanning settings.
690            mEmptyView.setText(briefText, BufferType.SPANNABLE);
691        } else {
692            // Append the description of scanning settings with link.
693            final StringBuilder contentBuilder = new StringBuilder();
694            contentBuilder.append(briefText);
695            contentBuilder.append("\n\n");
696            contentBuilder.append(getText(R.string.wifi_scan_notify_text));
697            LinkifyUtils.linkify(mEmptyView, contentBuilder, new LinkifyUtils.OnClickListener() {
698                @Override
699                public void onClick() {
700                    final SettingsActivity activity =
701                            (SettingsActivity) WifiSettings.this.getActivity();
702                    activity.startPreferencePanel(ScanningSettings.class.getName(), null,
703                            R.string.location_scanning_screen_title, null, null, 0);
704                }
705            });
706        }
707        // Embolden and enlarge the brief description anyway.
708        Spannable boldSpan = (Spannable) mEmptyView.getText();
709        boldSpan.setSpan(
710                new TextAppearanceSpan(getActivity(), android.R.style.TextAppearance_Medium), 0,
711                briefText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
712        getPreferenceScreen().removeAll();
713    }
714
715    private void addMessagePreference(int messageId) {
716        if (mEmptyView != null) mEmptyView.setText(messageId);
717        getPreferenceScreen().removeAll();
718    }
719
720    protected void setProgressBarVisible(boolean visible) {
721        if (mProgressHeader != null) {
722            mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE);
723        }
724    }
725
726    @Override
727    public void onWifiStateChanged(int state) {
728        Activity activity = getActivity();
729        if (activity != null) {
730            activity.invalidateOptionsMenu();
731        }
732
733        switch (state) {
734            case WifiManager.WIFI_STATE_ENABLING:
735                addMessagePreference(R.string.wifi_starting);
736                setProgressBarVisible(true);
737                break;
738
739            case WifiManager.WIFI_STATE_DISABLED:
740                setOffMessage();
741                setProgressBarVisible(false);
742                break;
743        }
744    }
745
746    @Override
747    public void onConnectedChanged() {
748        changeNextButtonState(mWifiTracker.isConnected());
749    }
750
751    /**
752     * Renames/replaces "Next" button when appropriate. "Next" button usually exists in
753     * Wifi setup screens, not in usual wifi settings screen.
754     *
755     * @param enabled true when the device is connected to a wifi network.
756     */
757    private void changeNextButtonState(boolean enabled) {
758        if (mEnableNextOnConnection && hasNextButton()) {
759            getNextButton().setEnabled(enabled);
760        }
761    }
762
763    @Override
764    public void onClick(DialogInterface dialogInterface, int button) {
765        if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) {
766            forget();
767        } else if (button == WifiDialog.BUTTON_SUBMIT) {
768            if (mDialog != null) {
769                submit(mDialog.getController());
770            }
771        }
772    }
773
774    /* package */ void submit(WifiConfigController configController) {
775
776        final WifiConfiguration config = configController.getConfig();
777
778        if (config == null) {
779            if (mSelectedAccessPoint != null
780                    && mSelectedAccessPoint.isSaved()) {
781                connect(mSelectedAccessPoint.getConfig());
782            }
783        } else if (config.networkId != INVALID_NETWORK_ID) {
784            if (mSelectedAccessPoint != null) {
785                mWifiManager.save(config, mSaveListener);
786            }
787        } else {
788            if (configController.isEdit()) {
789                mWifiManager.save(config, mSaveListener);
790            } else {
791                connect(config);
792            }
793        }
794
795        mWifiTracker.resumeScanning();
796    }
797
798    /* package */ void forget() {
799        MetricsLogger.action(getActivity(), MetricsLogger.ACTION_WIFI_FORGET);
800        if (!mSelectedAccessPoint.isSaved()) {
801            if (mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED) {
802                // Network is active but has no network ID - must be ephemeral.
803                mWifiManager.disableEphemeralNetwork(
804                        AccessPoint.convertToQuotedString(mSelectedAccessPoint.getSsid()));
805            } else {
806                // Should not happen, but a monkey seems to trigger it
807                Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig());
808                return;
809            }
810        } else {
811            mWifiManager.forget(mSelectedAccessPoint.getConfig().networkId, mForgetListener);
812        }
813
814        mWifiTracker.resumeScanning();
815
816        // We need to rename/replace "Next" button in wifi setup context.
817        changeNextButtonState(false);
818    }
819
820    protected void connect(final WifiConfiguration config) {
821        MetricsLogger.action(getActivity(), MetricsLogger.ACTION_WIFI_CONNECT);
822        mWifiManager.connect(config, mConnectListener);
823    }
824
825    protected void connect(final int networkId) {
826        MetricsLogger.action(getActivity(), MetricsLogger.ACTION_WIFI_CONNECT);
827        mWifiManager.connect(networkId, mConnectListener);
828    }
829
830    /**
831     * Refreshes acccess points and ask Wifi module to scan networks again.
832     */
833    /* package */ void refreshAccessPoints() {
834        mWifiTracker.resumeScanning();
835
836        getPreferenceScreen().removeAll();
837    }
838
839    /**
840     * Called when "add network" button is pressed.
841     */
842    /* package */ void onAddNetworkPressed() {
843        MetricsLogger.action(getActivity(), MetricsLogger.ACTION_WIFI_ADD_NETWORK);
844        // No exact access point is selected.
845        mSelectedAccessPoint = null;
846        showDialog(null, true);
847    }
848
849    /* package */ int getAccessPointsCount() {
850        final boolean wifiIsEnabled = mWifiTracker.isWifiEnabled();
851        if (wifiIsEnabled) {
852            return getPreferenceScreen().getPreferenceCount();
853        } else {
854            return 0;
855        }
856    }
857
858    /**
859     * Requests wifi module to pause wifi scan. May be ignored when the module is disabled.
860     */
861    /* package */ void pauseWifiScan() {
862        mWifiTracker.pauseScanning();
863    }
864
865    /**
866     * Requests wifi module to resume wifi scan. May be ignored when the module is disabled.
867     */
868    /* package */ void resumeWifiScan() {
869        mWifiTracker.resumeScanning();
870    }
871
872    @Override
873    protected int getHelpResource() {
874        return R.string.help_url_wifi;
875    }
876
877    @Override
878    public void onAccessPointChanged(AccessPoint accessPoint) {
879        ((AccessPointPreference) accessPoint.getTag()).refresh();
880    }
881
882    @Override
883    public void onLevelChanged(AccessPoint accessPoint) {
884        ((AccessPointPreference) accessPoint.getTag()).onLevelChanged();
885    }
886
887    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
888        new BaseSearchIndexProvider() {
889            @Override
890            public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
891                final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
892                final Resources res = context.getResources();
893
894                // Add fragment title
895                SearchIndexableRaw data = new SearchIndexableRaw(context);
896                data.title = res.getString(R.string.wifi_settings);
897                data.screenTitle = res.getString(R.string.wifi_settings);
898                data.keywords = res.getString(R.string.keywords_wifi);
899                result.add(data);
900
901                // Add saved Wi-Fi access points
902                final Collection<AccessPoint> accessPoints =
903                        WifiTracker.getCurrentAccessPoints(context, true, false, false);
904                for (AccessPoint accessPoint : accessPoints) {
905                    data = new SearchIndexableRaw(context);
906                    data.title = accessPoint.getSsid();
907                    data.screenTitle = res.getString(R.string.wifi_settings);
908                    data.enabled = enabled;
909                    result.add(data);
910                }
911
912                return result;
913            }
914        };
915
916    /**
917     * Returns true if the config is not editable/removable except by its creating Device Owner.
918     * @param config The WiFi config.
919     * @return true if the config is not editable/removable except by its creating Device Owner.
920     */
921    static boolean isEditabilityLockedDown(Context context, WifiConfiguration config) {
922        if (config == null) {
923            return false;
924        }
925        final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
926                Context.DEVICE_POLICY_SERVICE);
927        final String deviceOwnerPackageName = dpm.getDeviceOwner();
928        if (deviceOwnerPackageName == null) {
929            return false;
930        }
931        UserManager um = UserManager.get(context);
932        if (um.hasUserRestriction(UserManager.DISALLOW_CONFIG_WIFI)) {
933            return false;
934        }
935        final PackageManager pm = context.getPackageManager();
936        try {
937            final int deviceOwnerUid = pm.getPackageUid(deviceOwnerPackageName,
938                    UserHandle.getUserId(config.creatorUid));
939            return deviceOwnerUid == config.creatorUid;
940        } catch (NameNotFoundException e) {
941            return false;
942        }
943    }
944}
945