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