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