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