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