WifiSettings.java revision 9e9e63b7b3b31fcae7242eb07f528487e0913402
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.BroadcastReceiver;
25import android.content.ComponentName;
26import android.content.ContentResolver;
27import android.content.Context;
28import android.content.Intent;
29import android.content.IntentFilter;
30import android.content.pm.ApplicationInfo;
31import android.content.pm.IPackageManager;
32import android.content.pm.PackageManager;
33import android.content.pm.PackageManager.NameNotFoundException;
34import android.content.res.Resources;
35import android.graphics.drawable.Drawable;
36import android.net.ConnectivityManager;
37import android.net.NetworkInfo;
38import android.net.NetworkInfo.State;
39import android.net.wifi.WifiConfiguration;
40import android.net.wifi.WifiManager;
41import android.net.wifi.WpsInfo;
42import android.nfc.NfcAdapter;
43import android.os.Bundle;
44import android.os.HandlerThread;
45import android.os.Process;
46import android.os.RemoteException;
47import android.os.UserHandle;
48import android.provider.Settings;
49import android.support.v7.preference.Preference;
50import android.support.v7.preference.PreferenceViewHolder;
51import android.text.Spannable;
52import android.text.style.TextAppearanceSpan;
53import android.util.Log;
54import android.util.TypedValue;
55import android.view.ContextMenu;
56import android.view.ContextMenu.ContextMenuInfo;
57import android.view.Gravity;
58import android.view.Menu;
59import android.view.MenuInflater;
60import android.view.MenuItem;
61import android.view.View;
62import android.widget.ProgressBar;
63import android.widget.TextView;
64import android.widget.TextView.BufferType;
65import android.widget.Toast;
66
67import com.android.internal.logging.MetricsLogger;
68import com.android.internal.logging.MetricsProto.MetricsEvent;
69import com.android.settings.LinkifyUtils;
70import com.android.settings.R;
71import com.android.settings.RestrictedSettingsFragment;
72import com.android.settings.SettingsActivity;
73import com.android.settings.dashboard.SummaryLoader;
74import com.android.settings.location.ScanningSettings;
75import com.android.settings.search.BaseSearchIndexProvider;
76import com.android.settings.search.Indexable;
77import com.android.settings.search.SearchIndexableRaw;
78import com.android.settingslib.wifi.AccessPoint;
79import com.android.settingslib.wifi.AccessPoint.AccessPointListener;
80import com.android.settingslib.wifi.AccessPointPreference;
81import com.android.settingslib.wifi.WifiStatusTracker;
82import com.android.settingslib.wifi.WifiTracker;
83
84import java.util.ArrayList;
85import java.util.Collection;
86import java.util.List;
87
88import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
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 Indexable, WifiTracker.WifiListener, AccessPointListener,
100        WifiDialog.WifiDialogListener {
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_ADVANCED = Menu.FIRST + 4;
107    private static final int MENU_ID_SCAN = Menu.FIRST + 5;
108    private static final int MENU_ID_CONNECT = Menu.FIRST + 6;
109    private static final int MENU_ID_FORGET = Menu.FIRST + 7;
110    private static final int MENU_ID_MODIFY = Menu.FIRST + 8;
111    private static final int MENU_ID_WRITE_NFC = Menu.FIRST + 9;
112    private static final int MENU_ID_CONFIGURE = Menu.FIRST + 10;
113
114    public static final int WIFI_DIALOG_ID = 1;
115    /* package */ static final int WPS_PBC_DIALOG_ID = 2;
116    private static final int WPS_PIN_DIALOG_ID = 3;
117    private static final int WRITE_NFC_DIALOG_ID = 6;
118
119    // Instance state keys
120    private static final String SAVE_DIALOG_MODE = "dialog_mode";
121    private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
122    private static final String SAVED_WIFI_NFC_DIALOG_STATE = "wifi_nfc_dlg_state";
123
124    protected WifiManager mWifiManager;
125    private WifiManager.ActionListener mConnectListener;
126    private WifiManager.ActionListener mSaveListener;
127    private WifiManager.ActionListener mForgetListener;
128
129    private WifiEnabler mWifiEnabler;
130    // An access point being editted is stored here.
131    private AccessPoint mSelectedAccessPoint;
132
133    private WifiDialog mDialog;
134    private WriteWifiConfigToNfcDialog mWifiToNfcDialog;
135
136    private ProgressBar mProgressHeader;
137
138    // this boolean extra specifies whether to disable the Next button when not connected. Used by
139    // account creation outside of setup wizard.
140    private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
141    // This string extra specifies a network to open the connect dialog on, so the user can enter
142    // network credentials.  This is used by quick settings for secured networks.
143    private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
144
145    // should Next button only be enabled when we have a connection?
146    private boolean mEnableNextOnConnection;
147
148    // Save the dialog details
149    private int mDialogMode;
150    private AccessPoint mDlgAccessPoint;
151    private Bundle mAccessPointSavedState;
152    private Bundle mWifiNfcDialogSavedState;
153
154    private WifiTracker mWifiTracker;
155    private String mOpenSsid;
156
157    private HandlerThread mBgThread;
158
159    private AccessPointPreference.UserBadgeCache mUserBadgeCache;
160    private Preference mAddPreference;
161
162    private MenuItem mScanMenuItem;
163
164    /* End of "used in Wifi Setup context" */
165
166    public WifiSettings() {
167        super(DISALLOW_CONFIG_WIFI);
168    }
169
170    @Override
171    public void onViewCreated(View view, Bundle savedInstanceState) {
172        super.onViewCreated(view, savedInstanceState);
173        final Activity activity = getActivity();
174        if (activity != null) {
175            mProgressHeader = (ProgressBar) setPinnedHeaderView(R.layout.wifi_progress_header);
176        }
177    }
178
179    @Override
180    public void onCreate(Bundle icicle) {
181        super.onCreate(icicle);
182        addPreferencesFromResource(R.xml.wifi_settings);
183        mAddPreference = new Preference(getContext());
184        Drawable ic_add = getContext().getDrawable(R.drawable.ic_menu_add).mutate();
185        TypedValue tv = new TypedValue();
186        getContext().getTheme().resolveAttribute(android.R.attr.colorAccent, tv, true);
187        ic_add.setTint(getContext().getColor(tv.resourceId));
188        mAddPreference.setIcon(ic_add);
189        mAddPreference.setTitle(R.string.wifi_add_network);
190
191        mUserBadgeCache = new AccessPointPreference.UserBadgeCache(getPackageManager());
192
193        mBgThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
194        mBgThread.start();
195    }
196
197    @Override
198    public void onDestroy() {
199        mBgThread.quit();
200        super.onDestroy();
201    }
202
203    @Override
204    public void onActivityCreated(Bundle savedInstanceState) {
205        super.onActivityCreated(savedInstanceState);
206
207        mWifiTracker =
208                new WifiTracker(getActivity(), this, mBgThread.getLooper(), true, true, false);
209        mWifiManager = mWifiTracker.getManager();
210
211        mConnectListener = 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_connect_message,
221                                                Toast.LENGTH_SHORT).show();
222                                       }
223                                   }
224                               };
225
226        mSaveListener = 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_save_message,
236                                            Toast.LENGTH_SHORT).show();
237                                    }
238                                }
239                            };
240
241        mForgetListener = new WifiManager.ActionListener() {
242                                   @Override
243                                   public void onSuccess() {
244                                   }
245                                   @Override
246                                   public void onFailure(int reason) {
247                                       Activity activity = getActivity();
248                                       if (activity != null) {
249                                           Toast.makeText(activity,
250                                               R.string.wifi_failed_forget_message,
251                                               Toast.LENGTH_SHORT).show();
252                                       }
253                                   }
254                               };
255
256        if (savedInstanceState != null) {
257            mDialogMode = savedInstanceState.getInt(SAVE_DIALOG_MODE);
258            if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
259                mAccessPointSavedState =
260                    savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
261            }
262
263            if (savedInstanceState.containsKey(SAVED_WIFI_NFC_DIALOG_STATE)) {
264                mWifiNfcDialogSavedState =
265                    savedInstanceState.getBundle(SAVED_WIFI_NFC_DIALOG_STATE);
266            }
267        }
268
269        // if we're supposed to enable/disable the Next button based on our current connection
270        // state, start it off in the right state
271        Intent intent = getActivity().getIntent();
272        mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
273
274        if (mEnableNextOnConnection) {
275            if (hasNextButton()) {
276                final ConnectivityManager connectivity = (ConnectivityManager)
277                        getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
278                if (connectivity != null) {
279                    NetworkInfo info = connectivity.getNetworkInfo(
280                            ConnectivityManager.TYPE_WIFI);
281                    changeNextButtonState(info.isConnected());
282                }
283            }
284        }
285
286        registerForContextMenu(getListView());
287        setHasOptionsMenu(true);
288
289        if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) {
290            mOpenSsid = intent.getStringExtra(EXTRA_START_CONNECT_SSID);
291            onAccessPointsChanged();
292        }
293    }
294
295    @Override
296    public void onDestroyView() {
297        super.onDestroyView();
298
299        if (mWifiEnabler != null) {
300            mWifiEnabler.teardownSwitchBar();
301        }
302    }
303
304    @Override
305    public void onStart() {
306        super.onStart();
307
308        // On/off switch is hidden for Setup Wizard (returns null)
309        mWifiEnabler = createWifiEnabler();
310    }
311
312    /**
313     * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard)
314     */
315    /* package */ WifiEnabler createWifiEnabler() {
316        final SettingsActivity activity = (SettingsActivity) getActivity();
317        return new WifiEnabler(activity, activity.getSwitchBar());
318    }
319
320    @Override
321    public void onResume() {
322        final Activity activity = getActivity();
323        super.onResume();
324        removePreference("dummy");
325        if (mWifiEnabler != null) {
326            mWifiEnabler.resume(activity);
327        }
328
329        mWifiTracker.startTracking();
330        activity.invalidateOptionsMenu();
331    }
332
333    @Override
334    public void onPause() {
335        super.onPause();
336        if (mWifiEnabler != null) {
337            mWifiEnabler.pause();
338        }
339
340        mWifiTracker.stopTracking();
341    }
342
343    @Override
344    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
345        // If the user is not allowed to configure wifi, do not show the menu.
346        if (isUiRestricted()) return;
347
348        addOptionsMenuItems(menu);
349        super.onCreateOptionsMenu(menu, inflater);
350    }
351
352    /**
353     * @param menu
354     */
355    void addOptionsMenuItems(Menu menu) {
356        final boolean wifiIsEnabled = mWifiTracker.isWifiEnabled();
357        mScanMenuItem = menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.menu_stats_refresh);
358        mScanMenuItem.setEnabled(wifiIsEnabled)
359               .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
360        menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
361                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
362        menu.add(Menu.NONE, MENU_ID_CONFIGURE, 0, R.string.wifi_menu_configure)
363                .setIcon(R.drawable.ic_settings_24dp)
364                .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
365    }
366
367    @Override
368    protected int getMetricsCategory() {
369        return MetricsEvent.WIFI;
370    }
371
372    @Override
373    public void onSaveInstanceState(Bundle outState) {
374        super.onSaveInstanceState(outState);
375
376        // If the dialog is showing, save its state.
377        if (mDialog != null && mDialog.isShowing()) {
378            outState.putInt(SAVE_DIALOG_MODE, mDialogMode);
379            if (mDlgAccessPoint != null) {
380                mAccessPointSavedState = new Bundle();
381                mDlgAccessPoint.saveWifiState(mAccessPointSavedState);
382                outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
383            }
384        }
385
386        if (mWifiToNfcDialog != null && mWifiToNfcDialog.isShowing()) {
387            Bundle savedState = new Bundle();
388            mWifiToNfcDialog.saveState(savedState);
389            outState.putBundle(SAVED_WIFI_NFC_DIALOG_STATE, savedState);
390        }
391    }
392
393    @Override
394    public boolean onOptionsItemSelected(MenuItem item) {
395        // If the user is not allowed to configure wifi, do not handle menu selections.
396        if (isUiRestricted()) return false;
397
398        switch (item.getItemId()) {
399            case MENU_ID_WPS_PBC:
400                showDialog(WPS_PBC_DIALOG_ID);
401                return true;
402                /*
403            case MENU_ID_P2P:
404                if (getActivity() instanceof SettingsActivity) {
405                    ((SettingsActivity) getActivity()).startPreferencePanel(
406                            WifiP2pSettings.class.getCanonicalName(),
407                            null,
408                            R.string.wifi_p2p_settings_title, null,
409                            this, 0);
410                } else {
411                    startFragment(this, WifiP2pSettings.class.getCanonicalName(),
412                            R.string.wifi_p2p_settings_title, -1, null);
413                }
414                return true;
415                */
416            case MENU_ID_WPS_PIN:
417                showDialog(WPS_PIN_DIALOG_ID);
418                return true;
419            case MENU_ID_SCAN:
420                MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_FORCE_SCAN);
421                mWifiTracker.forceScan();
422                return true;
423            case MENU_ID_ADVANCED:
424                if (getActivity() instanceof SettingsActivity) {
425                    ((SettingsActivity) getActivity()).startPreferencePanel(
426                            AdvancedWifiSettings.class.getCanonicalName(), null,
427                            R.string.wifi_advanced_titlebar, null, this, 0);
428                } else {
429                    startFragment(this, AdvancedWifiSettings.class.getCanonicalName(),
430                            R.string.wifi_advanced_titlebar, -1 /* Do not request a results */,
431                            null);
432                }
433                return true;
434            case MENU_ID_CONFIGURE:
435                if (getActivity() instanceof SettingsActivity) {
436                    ((SettingsActivity) getActivity()).startPreferencePanel(
437                            ConfigureWifiSettings.class.getCanonicalName(), null,
438                            R.string.wifi_configure_titlebar, null, this, 0);
439                } else {
440                    startFragment(this, ConfigureWifiSettings.class.getCanonicalName(),
441                            R.string.wifi_configure_titlebar, -1 /* Do not request a results */,
442                            null);
443                }
444                return true;
445
446        }
447        return super.onOptionsItemSelected(item);
448    }
449
450    @Override
451    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
452            Preference preference = (Preference) view.getTag();
453
454            if (preference instanceof LongPressAccessPointPreference) {
455                mSelectedAccessPoint =
456                        ((LongPressAccessPointPreference) preference).getAccessPoint();
457                menu.setHeaderTitle(mSelectedAccessPoint.getSsid());
458                if (mSelectedAccessPoint.isConnectable()) {
459                    menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
460                }
461
462                WifiConfiguration config = mSelectedAccessPoint.getConfig();
463                // Some configs are ineditable
464                if (isEditabilityLockedDown(getActivity(), config)) {
465                    return;
466                }
467
468                if (mSelectedAccessPoint.isSaved() || mSelectedAccessPoint.isEphemeral()) {
469                    // Allow forgetting a network if either the network is saved or ephemerally
470                    // connected. (In the latter case, "forget" blacklists the network so it won't
471                    // be used again, ephemerally).
472                    menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
473                }
474                if (mSelectedAccessPoint.isSaved()) {
475                    menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
476                    NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
477                    if (nfcAdapter != null && nfcAdapter.isEnabled() &&
478                            mSelectedAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
479                        // Only allow writing of NFC tags for password-protected networks.
480                        menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc);
481                    }
482                }
483            }
484    }
485
486    @Override
487    public boolean onContextItemSelected(MenuItem item) {
488        if (mSelectedAccessPoint == null) {
489            return super.onContextItemSelected(item);
490        }
491        switch (item.getItemId()) {
492            case MENU_ID_CONNECT: {
493                if (mSelectedAccessPoint.isSaved()) {
494                    connect(mSelectedAccessPoint.getConfig());
495                } else if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE) {
496                    /** Bypass dialog for unsecured networks */
497                    mSelectedAccessPoint.generateOpenNetworkConfig();
498                    connect(mSelectedAccessPoint.getConfig());
499                } else {
500                    showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_CONNECT);
501                }
502                return true;
503            }
504            case MENU_ID_FORGET: {
505                forget();
506                return true;
507            }
508            case MENU_ID_MODIFY: {
509                showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_MODIFY);
510                return true;
511            }
512            case MENU_ID_WRITE_NFC:
513                showDialog(WRITE_NFC_DIALOG_ID);
514                return true;
515
516        }
517        return super.onContextItemSelected(item);
518    }
519
520    @Override
521    public boolean onPreferenceTreeClick(Preference preference) {
522        if (preference instanceof LongPressAccessPointPreference) {
523            mSelectedAccessPoint = ((LongPressAccessPointPreference) preference).getAccessPoint();
524            if (mSelectedAccessPoint == null) {
525                return false;
526            }
527            /** Bypass dialog for unsecured, unsaved, and inactive networks */
528            if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE &&
529                    !mSelectedAccessPoint.isSaved() && !mSelectedAccessPoint.isActive()) {
530                mSelectedAccessPoint.generateOpenNetworkConfig();
531                connect(mSelectedAccessPoint.getConfig());
532            } else if (mSelectedAccessPoint.isSaved()) {
533                showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_VIEW);
534            } else {
535                showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_CONNECT);
536            }
537        } else if (preference == mAddPreference) {
538            onAddNetworkPressed();
539        } else {
540            return super.onPreferenceTreeClick(preference);
541        }
542        return true;
543    }
544
545    private void showDialog(AccessPoint accessPoint, int dialogMode) {
546        if (accessPoint != null) {
547            WifiConfiguration config = accessPoint.getConfig();
548            if (isEditabilityLockedDown(getActivity(), config) && accessPoint.isActive()) {
549                final int userId = UserHandle.getUserId(config.creatorUid);
550                final PackageManager pm = getActivity().getPackageManager();
551                final IPackageManager ipm = AppGlobals.getPackageManager();
552                String appName = pm.getNameForUid(config.creatorUid);
553                try {
554                    final ApplicationInfo appInfo = ipm.getApplicationInfo(appName, /* flags */ 0,
555                            userId);
556                    final CharSequence label = pm.getApplicationLabel(appInfo);
557                    if (label != null) {
558                        appName = label.toString();
559                    }
560                } catch (RemoteException e) {
561                    // leave appName as packageName
562                }
563                final AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
564                builder.setTitle(accessPoint.getSsid())
565                        .setMessage(getString(R.string.wifi_alert_lockdown_by_device_owner,
566                                appName))
567                        .setPositiveButton(android.R.string.ok, null)
568                        .show();
569                return;
570            }
571        }
572
573        if (mDialog != null) {
574            removeDialog(WIFI_DIALOG_ID);
575            mDialog = null;
576        }
577
578        // Save the access point and edit mode
579        mDlgAccessPoint = accessPoint;
580        mDialogMode = dialogMode;
581
582        showDialog(WIFI_DIALOG_ID);
583    }
584
585    @Override
586    public Dialog onCreateDialog(int dialogId) {
587        switch (dialogId) {
588            case WIFI_DIALOG_ID:
589                AccessPoint ap = mDlgAccessPoint; // For manual launch
590                if (ap == null) { // For re-launch from saved state
591                    if (mAccessPointSavedState != null) {
592                        ap = new AccessPoint(getActivity(), mAccessPointSavedState);
593                        // For repeated orientation changes
594                        mDlgAccessPoint = ap;
595                        // Reset the saved access point data
596                        mAccessPointSavedState = null;
597                    }
598                }
599                // If it's null, fine, it's for Add Network
600                mSelectedAccessPoint = ap;
601                final boolean hideForget = (ap == null || isEditabilityLockedDown(getActivity(),
602                        ap.getConfig()));
603                mDialog = new WifiDialog(getActivity(), this, ap, mDialogMode,
604                        /* no hide submit/connect */ false,
605                        /* hide forget if config locked down */ hideForget);
606                return mDialog;
607            case WPS_PBC_DIALOG_ID:
608                return new WpsDialog(getActivity(), WpsInfo.PBC);
609            case WPS_PIN_DIALOG_ID:
610                return new WpsDialog(getActivity(), WpsInfo.DISPLAY);
611            case WRITE_NFC_DIALOG_ID:
612                if (mSelectedAccessPoint != null) {
613                    mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
614                            getActivity(), mSelectedAccessPoint.getConfig().networkId,
615                            mSelectedAccessPoint.getSecurity(),
616                            mWifiManager);
617                } else if (mWifiNfcDialogSavedState != null) {
618                    mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
619                            getActivity(), mWifiNfcDialogSavedState, mWifiManager);
620                }
621
622                return mWifiToNfcDialog;
623        }
624        return super.onCreateDialog(dialogId);
625    }
626
627    /**
628     * Shows the latest access points available with supplemental information like
629     * the strength of network and the security for it.
630     */
631    @Override
632    public void onAccessPointsChanged() {
633        // Safeguard from some delayed event handling
634        if (getActivity() == null) return;
635        if (isUiRestricted()) {
636            if (!isUiRestrictedByOnlyAdmin()) {
637                addMessagePreference(R.string.wifi_empty_list_user_restricted);
638            }
639            getPreferenceScreen().removeAll();
640            return;
641        }
642        final int wifiState = mWifiManager.getWifiState();
643
644        switch (wifiState) {
645            case WifiManager.WIFI_STATE_ENABLED:
646                // AccessPoints are automatically sorted with TreeSet.
647                final Collection<AccessPoint> accessPoints =
648                        mWifiTracker.getAccessPoints();
649                getPreferenceScreen().removeAll();
650
651                boolean hasAvailableAccessPoints = false;
652                int index = 0;
653                for (AccessPoint accessPoint : accessPoints) {
654                    // Ignore access points that are out of range.
655                    if (accessPoint.getLevel() != -1) {
656                        hasAvailableAccessPoints = true;
657                        if (accessPoint.getTag() != null) {
658                            final Preference pref = (Preference) accessPoint.getTag();
659                            pref.setOrder(index++);
660                            getPreferenceScreen().addPreference(pref);
661                            continue;
662                        }
663                        LongPressAccessPointPreference
664                                preference = new LongPressAccessPointPreference(accessPoint,
665                                getPrefContext(), mUserBadgeCache, false, this);
666                        preference.setOrder(index++);
667
668                        if (mOpenSsid != null && mOpenSsid.equals(accessPoint.getSsidStr())
669                                && !accessPoint.isSaved()
670                                && accessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
671                            onPreferenceTreeClick(preference);
672                            mOpenSsid = null;
673                        }
674                        getPreferenceScreen().addPreference(preference);
675                        accessPoint.setListener(this);
676                    }
677                }
678                if (!hasAvailableAccessPoints) {
679                    setProgressBarVisible(true);
680                    Preference pref = new Preference(getContext()) {
681                        @Override
682                        public void onBindViewHolder(PreferenceViewHolder holder) {
683                            super.onBindViewHolder(holder);
684                            // Show a line on each side of add network.
685                            holder.setDividerAllowedBelow(true);
686                        }
687                    };
688                    pref.setSelectable(false);
689                    pref.setSummary(R.string.wifi_empty_list_wifi_on);
690                    pref.setOrder(0);
691                    getPreferenceScreen().addPreference(pref);
692                    mAddPreference.setOrder(1);
693                    getPreferenceScreen().addPreference(mAddPreference);
694                } else {
695                    mAddPreference.setOrder(index++);
696                    getPreferenceScreen().addPreference(mAddPreference);
697                    setProgressBarVisible(false);
698                }
699                if (mScanMenuItem != null) {
700                    mScanMenuItem.setEnabled(true);
701                }
702                break;
703
704            case WifiManager.WIFI_STATE_ENABLING:
705                getPreferenceScreen().removeAll();
706                setProgressBarVisible(true);
707                break;
708
709            case WifiManager.WIFI_STATE_DISABLING:
710                addMessagePreference(R.string.wifi_stopping);
711                setProgressBarVisible(true);
712                break;
713
714            case WifiManager.WIFI_STATE_DISABLED:
715                setOffMessage();
716                setProgressBarVisible(false);
717                if (mScanMenuItem != null) {
718                    mScanMenuItem.setEnabled(false);
719                }
720                break;
721        }
722    }
723
724    @Override
725    protected TextView initEmptyTextView() {
726        TextView emptyView = (TextView) getActivity().findViewById(android.R.id.empty);
727        emptyView.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
728        return emptyView;
729    }
730
731    @Override
732    protected View initAdminSupportDetailsView() {
733        return getActivity().findViewById(R.id.admin_support_details);
734    }
735
736    private void setOffMessage() {
737        if (isUiRestricted()) {
738            if (!isUiRestrictedByOnlyAdmin()) {
739                addMessagePreference(R.string.wifi_empty_list_user_restricted);
740            }
741            getPreferenceScreen().removeAll();
742            return;
743        }
744
745        TextView emptyTextView = getEmptyTextView();
746        if (emptyTextView == null) {
747            return;
748        }
749
750        final CharSequence briefText = getText(R.string.wifi_empty_list_wifi_off);
751
752        // Don't use WifiManager.isScanAlwaysAvailable() to check the Wi-Fi scanning mode. Instead,
753        // read the system settings directly. Because when the device is in Airplane mode, even if
754        // Wi-Fi scanning mode is on, WifiManager.isScanAlwaysAvailable() still returns "off".
755        final ContentResolver resolver = getActivity().getContentResolver();
756        final boolean wifiScanningMode = Settings.Global.getInt(
757                resolver, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1;
758
759        if (!wifiScanningMode) {
760            // Show only the brief text if the user is not allowed to configure scanning settings,
761            // or the scanning mode has been turned off.
762            emptyTextView.setText(briefText, BufferType.SPANNABLE);
763        } else {
764            // Append the description of scanning settings with link.
765            final StringBuilder contentBuilder = new StringBuilder();
766            contentBuilder.append(briefText);
767            contentBuilder.append("\n\n");
768            contentBuilder.append(getText(R.string.wifi_scan_notify_text));
769            LinkifyUtils.linkify(emptyTextView, contentBuilder, new LinkifyUtils.OnClickListener() {
770                @Override
771                public void onClick() {
772                    final SettingsActivity activity =
773                            (SettingsActivity) WifiSettings.this.getActivity();
774                    activity.startPreferencePanel(ScanningSettings.class.getName(), null,
775                            R.string.location_scanning_screen_title, null, null, 0);
776                }
777            });
778        }
779        // Embolden and enlarge the brief description anyway.
780        Spannable boldSpan = (Spannable) emptyTextView.getText();
781        boldSpan.setSpan(
782                new TextAppearanceSpan(getActivity(), android.R.style.TextAppearance_Medium), 0,
783                briefText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
784        getPreferenceScreen().removeAll();
785    }
786
787    private void addMessagePreference(int messageId) {
788        TextView emptyTextView = getEmptyTextView();
789        if (emptyTextView != null) emptyTextView.setText(messageId);
790        getPreferenceScreen().removeAll();
791    }
792
793    protected void setProgressBarVisible(boolean visible) {
794        if (mProgressHeader != null) {
795            mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE);
796        }
797    }
798
799    @Override
800    public void onWifiStateChanged(int state) {
801        switch (state) {
802            case WifiManager.WIFI_STATE_ENABLING:
803                addMessagePreference(R.string.wifi_starting);
804                setProgressBarVisible(true);
805                break;
806
807            case WifiManager.WIFI_STATE_DISABLED:
808                setOffMessage();
809                setProgressBarVisible(false);
810                break;
811        }
812    }
813
814    @Override
815    public void onConnectedChanged() {
816        changeNextButtonState(mWifiTracker.isConnected());
817    }
818
819    /**
820     * Renames/replaces "Next" button when appropriate. "Next" button usually exists in
821     * Wifi setup screens, not in usual wifi settings screen.
822     *
823     * @param enabled true when the device is connected to a wifi network.
824     */
825    private void changeNextButtonState(boolean enabled) {
826        if (mEnableNextOnConnection && hasNextButton()) {
827            getNextButton().setEnabled(enabled);
828        }
829    }
830
831    @Override
832    public void onForget(WifiDialog dialog) {
833        forget();
834    }
835
836    @Override
837    public void onSubmit(WifiDialog dialog) {
838        if (mDialog != null) {
839            submit(mDialog.getController());
840        }
841    }
842
843    /* package */ void submit(WifiConfigController configController) {
844
845        final WifiConfiguration config = configController.getConfig();
846
847        if (config == null) {
848            if (mSelectedAccessPoint != null
849                    && mSelectedAccessPoint.isSaved()) {
850                connect(mSelectedAccessPoint.getConfig());
851            }
852        } else if (configController.getMode() == WifiConfigUiBase.MODE_MODIFY) {
853            mWifiManager.save(config, mSaveListener);
854        } else {
855            mWifiManager.save(config, mSaveListener);
856            if (mSelectedAccessPoint != null) { // Not an "Add network"
857                connect(config);
858            }
859        }
860
861        mWifiTracker.resumeScanning();
862    }
863
864    /* package */ void forget() {
865        MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_FORGET);
866        if (!mSelectedAccessPoint.isSaved()) {
867            if (mSelectedAccessPoint.getNetworkInfo() != null &&
868                    mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED) {
869                // Network is active but has no network ID - must be ephemeral.
870                mWifiManager.disableEphemeralNetwork(
871                        AccessPoint.convertToQuotedString(mSelectedAccessPoint.getSsidStr()));
872            } else {
873                // Should not happen, but a monkey seems to trigger it
874                Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig());
875                return;
876            }
877        } else {
878            mWifiManager.forget(mSelectedAccessPoint.getConfig().networkId, mForgetListener);
879        }
880
881        mWifiTracker.resumeScanning();
882
883        // We need to rename/replace "Next" button in wifi setup context.
884        changeNextButtonState(false);
885    }
886
887    protected void connect(final WifiConfiguration config) {
888        MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT);
889        mWifiManager.connect(config, mConnectListener);
890    }
891
892    protected void connect(final int networkId) {
893        MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT);
894        mWifiManager.connect(networkId, mConnectListener);
895    }
896
897    /**
898     * Called when "add network" button is pressed.
899     */
900    /* package */ void onAddNetworkPressed() {
901        MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_ADD_NETWORK);
902        // No exact access point is selected.
903        mSelectedAccessPoint = null;
904        showDialog(null, WifiConfigUiBase.MODE_CONNECT);
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        ((LongPressAccessPointPreference) accessPoint.getTag()).refresh();
915    }
916
917    @Override
918    public void onLevelChanged(AccessPoint accessPoint) {
919        ((LongPressAccessPointPreference) 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<>();
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 ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnAnyUser();
986            if (deviceOwner != null) {
987                final int deviceOwnerUserId = dpm.getDeviceOwnerUserId();
988                try {
989                    final int deviceOwnerUid = pm.getPackageUidAsUser(deviceOwner.getPackageName(),
990                            deviceOwnerUserId);
991                    isConfigEligibleForLockdown = deviceOwnerUid == config.creatorUid;
992                } catch (NameNotFoundException e) {
993                    // don't care
994                }
995            }
996        }
997        if (!isConfigEligibleForLockdown) {
998            return true;
999        }
1000
1001        final ContentResolver resolver = context.getContentResolver();
1002        final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
1003                Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
1004        return !isLockdownFeatureEnabled;
1005    }
1006
1007    private static class SummaryProvider extends BroadcastReceiver
1008            implements SummaryLoader.SummaryProvider {
1009
1010        private final Context mContext;
1011        private final WifiManager mWifiManager;
1012        private final WifiStatusTracker mWifiTracker;
1013        private final SummaryLoader mSummaryLoader;
1014
1015        public SummaryProvider(Context context, SummaryLoader summaryLoader) {
1016            mContext = context;
1017            mSummaryLoader = summaryLoader;
1018            mWifiManager = context.getSystemService(WifiManager.class);
1019            mWifiTracker = new WifiStatusTracker(mWifiManager);
1020        }
1021
1022        private CharSequence getSummary() {
1023            if (!mWifiTracker.enabled) {
1024                return mContext.getString(R.string.disabled);
1025            }
1026            if (!mWifiTracker.connected) {
1027                return mContext.getString(R.string.disconnected);
1028            }
1029            return mWifiTracker.ssid;
1030        }
1031
1032        @Override
1033        public void setListening(boolean listening) {
1034            if (listening) {
1035                IntentFilter filter = new IntentFilter();
1036                filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
1037                filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
1038                filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
1039                mContext.registerReceiver(this, filter);
1040            } else {
1041                mContext.unregisterReceiver(this);
1042            }
1043        }
1044
1045        @Override
1046        public void onReceive(Context context, Intent intent) {
1047            mWifiTracker.handleBroadcast(intent);
1048            mSummaryLoader.setSummary(this, getSummary());
1049        }
1050    }
1051
1052    public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
1053            = new SummaryLoader.SummaryProviderFactory() {
1054        @Override
1055        public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
1056                                                                   SummaryLoader summaryLoader) {
1057            return new SummaryProvider(activity, summaryLoader);
1058        }
1059    };
1060}
1061