WifiSettings.java revision 55601622d5ddd9d9ef0ace09ade96e999879bff7
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.getSsidStr())
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
712        // Don't use WifiManager.isScanAlwaysAvailable() to check the Wi-Fi scanning mode. Instead,
713        // read the system settings directly. Because when the device is in Airplane mode, even if
714        // Wi-Fi scanning mode is on, WifiManager.isScanAlwaysAvailable() still returns "off".
715        final ContentResolver resolver = getActivity().getContentResolver();
716        final boolean wifiScanningMode = Settings.Global.getInt(
717                resolver, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1;
718
719        if (isUiRestricted() || !wifiScanningMode) {
720            // Show only the brief text if the user is not allowed to configure scanning settings,
721            // or the scanning mode has been turned off.
722            mEmptyView.setText(briefText, BufferType.SPANNABLE);
723        } else {
724            // Append the description of scanning settings with link.
725            final StringBuilder contentBuilder = new StringBuilder();
726            contentBuilder.append(briefText);
727            contentBuilder.append("\n\n");
728            contentBuilder.append(getText(R.string.wifi_scan_notify_text));
729            LinkifyUtils.linkify(mEmptyView, contentBuilder, new LinkifyUtils.OnClickListener() {
730                @Override
731                public void onClick() {
732                    final SettingsActivity activity =
733                            (SettingsActivity) WifiSettings.this.getActivity();
734                    activity.startPreferencePanel(ScanningSettings.class.getName(), null,
735                            R.string.location_scanning_screen_title, null, null, 0);
736                }
737            });
738        }
739        // Embolden and enlarge the brief description anyway.
740        Spannable boldSpan = (Spannable) mEmptyView.getText();
741        boldSpan.setSpan(
742                new TextAppearanceSpan(getActivity(), android.R.style.TextAppearance_Medium), 0,
743                briefText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
744        getPreferenceScreen().removeAll();
745    }
746
747    private void addMessagePreference(int messageId) {
748        if (mEmptyView != null) mEmptyView.setText(messageId);
749        getPreferenceScreen().removeAll();
750    }
751
752    protected void setProgressBarVisible(boolean visible) {
753        if (mProgressHeader != null) {
754            mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE);
755        }
756    }
757
758    @Override
759    public void onWifiStateChanged(int state) {
760        Activity activity = getActivity();
761        if (activity != null) {
762            activity.invalidateOptionsMenu();
763        }
764
765        switch (state) {
766            case WifiManager.WIFI_STATE_ENABLING:
767                addMessagePreference(R.string.wifi_starting);
768                setProgressBarVisible(true);
769                break;
770
771            case WifiManager.WIFI_STATE_DISABLED:
772                setOffMessage();
773                setProgressBarVisible(false);
774                break;
775        }
776    }
777
778    @Override
779    public void onConnectedChanged() {
780        changeNextButtonState(mWifiTracker.isConnected());
781    }
782
783    /**
784     * Renames/replaces "Next" button when appropriate. "Next" button usually exists in
785     * Wifi setup screens, not in usual wifi settings screen.
786     *
787     * @param enabled true when the device is connected to a wifi network.
788     */
789    private void changeNextButtonState(boolean enabled) {
790        if (mEnableNextOnConnection && hasNextButton()) {
791            getNextButton().setEnabled(enabled);
792        }
793    }
794
795    @Override
796    public void onClick(DialogInterface dialogInterface, int button) {
797        if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) {
798            forget();
799        } else if (button == WifiDialog.BUTTON_SUBMIT) {
800            if (mDialog != null) {
801                submit(mDialog.getController());
802            }
803        }
804    }
805
806    /* package */ void submit(WifiConfigController configController) {
807
808        final WifiConfiguration config = configController.getConfig();
809
810        if (config == null) {
811            if (mSelectedAccessPoint != null
812                    && mSelectedAccessPoint.isSaved()) {
813                connect(mSelectedAccessPoint.getConfig());
814            }
815        } else if (config.networkId != INVALID_NETWORK_ID) {
816            if (mSelectedAccessPoint != null) {
817                mWifiManager.save(config, mSaveListener);
818            }
819        } else {
820            if (configController.isEdit()) {
821                mWifiManager.save(config, mSaveListener);
822            } else {
823                connect(config);
824            }
825        }
826
827        mWifiTracker.resumeScanning();
828    }
829
830    /* package */ void forget() {
831        MetricsLogger.action(getActivity(), MetricsLogger.ACTION_WIFI_FORGET);
832        if (!mSelectedAccessPoint.isSaved()) {
833            if (mSelectedAccessPoint.getNetworkInfo() != null &&
834                    mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED) {
835                // Network is active but has no network ID - must be ephemeral.
836                mWifiManager.disableEphemeralNetwork(
837                        AccessPoint.convertToQuotedString(mSelectedAccessPoint.getSsidStr()));
838            } else {
839                // Should not happen, but a monkey seems to trigger it
840                Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig());
841                return;
842            }
843        } else {
844            mWifiManager.forget(mSelectedAccessPoint.getConfig().networkId, mForgetListener);
845        }
846
847        mWifiTracker.resumeScanning();
848
849        // We need to rename/replace "Next" button in wifi setup context.
850        changeNextButtonState(false);
851    }
852
853    protected void connect(final WifiConfiguration config) {
854        MetricsLogger.action(getActivity(), MetricsLogger.ACTION_WIFI_CONNECT);
855        mWifiManager.connect(config, mConnectListener);
856    }
857
858    protected void connect(final int networkId) {
859        MetricsLogger.action(getActivity(), MetricsLogger.ACTION_WIFI_CONNECT);
860        mWifiManager.connect(networkId, mConnectListener);
861    }
862
863    /**
864     * Refreshes acccess points and ask Wifi module to scan networks again.
865     */
866    /* package */ void refreshAccessPoints() {
867        mWifiTracker.resumeScanning();
868
869        getPreferenceScreen().removeAll();
870    }
871
872    /**
873     * Called when "add network" button is pressed.
874     */
875    /* package */ void onAddNetworkPressed() {
876        MetricsLogger.action(getActivity(), MetricsLogger.ACTION_WIFI_ADD_NETWORK);
877        // No exact access point is selected.
878        mSelectedAccessPoint = null;
879        showDialog(null, true);
880    }
881
882    /* package */ int getAccessPointsCount() {
883        final boolean wifiIsEnabled = mWifiTracker.isWifiEnabled();
884        if (wifiIsEnabled) {
885            return getPreferenceScreen().getPreferenceCount();
886        } else {
887            return 0;
888        }
889    }
890
891    /**
892     * Requests wifi module to pause wifi scan. May be ignored when the module is disabled.
893     */
894    /* package */ void pauseWifiScan() {
895        mWifiTracker.pauseScanning();
896    }
897
898    /**
899     * Requests wifi module to resume wifi scan. May be ignored when the module is disabled.
900     */
901    /* package */ void resumeWifiScan() {
902        mWifiTracker.resumeScanning();
903    }
904
905    @Override
906    protected int getHelpResource() {
907        return R.string.help_url_wifi;
908    }
909
910    @Override
911    public void onAccessPointChanged(AccessPoint accessPoint) {
912        ((AccessPointPreference) accessPoint.getTag()).refresh();
913    }
914
915    @Override
916    public void onLevelChanged(AccessPoint accessPoint) {
917        ((AccessPointPreference) accessPoint.getTag()).onLevelChanged();
918    }
919
920    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
921        new BaseSearchIndexProvider() {
922            @Override
923            public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
924                final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>();
925                final Resources res = context.getResources();
926
927                // Add fragment title
928                SearchIndexableRaw data = new SearchIndexableRaw(context);
929                data.title = res.getString(R.string.wifi_settings);
930                data.screenTitle = res.getString(R.string.wifi_settings);
931                data.keywords = res.getString(R.string.keywords_wifi);
932                result.add(data);
933
934                // Add saved Wi-Fi access points
935                final Collection<AccessPoint> accessPoints =
936                        WifiTracker.getCurrentAccessPoints(context, true, false, false);
937                for (AccessPoint accessPoint : accessPoints) {
938                    data = new SearchIndexableRaw(context);
939                    data.title = accessPoint.getSsidStr();
940                    data.screenTitle = res.getString(R.string.wifi_settings);
941                    data.enabled = enabled;
942                    result.add(data);
943                }
944
945                return result;
946            }
947        };
948
949    /**
950     * Returns true if the config is not editable through Settings.
951     * @param context Context of caller
952     * @param config The WiFi config.
953     * @return true if the config is not editable through Settings.
954     */
955    static boolean isEditabilityLockedDown(Context context, WifiConfiguration config) {
956        return !canModifyNetwork(context, config);
957    }
958
959    /**
960     * This method is a stripped version of WifiConfigStore.canModifyNetwork.
961     * TODO: refactor to have only one method.
962     * @param context Context of caller
963     * @param config The WiFi config.
964     * @return true if Settings can modify the config.
965     */
966    static boolean canModifyNetwork(Context context, WifiConfiguration config) {
967        if (config == null) {
968            return true;
969        }
970
971        final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
972                Context.DEVICE_POLICY_SERVICE);
973
974        // Check if device has DPM capability. If it has and dpm is still null, then we
975        // treat this case with suspicion and bail out.
976        final PackageManager pm = context.getPackageManager();
977        if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) && dpm == null) {
978            return false;
979        }
980
981        boolean isConfigEligibleForLockdown = false;
982        if (dpm != null) {
983            final String deviceOwnerPackageName = dpm.getDeviceOwner();
984            if (deviceOwnerPackageName != null) {
985                try {
986                    final int deviceOwnerUid = pm.getPackageUid(deviceOwnerPackageName,
987                            UserHandle.USER_OWNER);
988                    isConfigEligibleForLockdown = deviceOwnerUid == config.creatorUid;
989                } catch (NameNotFoundException e) {
990                    // don't care
991                }
992            }
993        }
994        if (!isConfigEligibleForLockdown) {
995            return true;
996        }
997
998        final ContentResolver resolver = context.getContentResolver();
999        final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
1000                Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
1001        return !isLockdownFeatureEnabled;
1002    }
1003
1004}
1005