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