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