WifiSettings.java revision f35a55dee468be819b924b1f8cfb575d5f74c4a9
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.annotation.NonNull;
20import android.app.Activity;
21import android.app.Dialog;
22import android.app.admin.DevicePolicyManager;
23import android.content.ComponentName;
24import android.content.ContentResolver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.pm.PackageManager;
28import android.content.pm.PackageManager.NameNotFoundException;
29import android.content.res.Resources;
30import android.net.ConnectivityManager;
31import android.net.NetworkInfo;
32import android.net.NetworkInfo.State;
33import android.net.wifi.WifiConfiguration;
34import android.net.wifi.WifiManager;
35import android.net.wifi.WpsInfo;
36import android.nfc.NfcAdapter;
37import android.os.Bundle;
38import android.os.HandlerThread;
39import android.os.Process;
40import android.provider.Settings;
41import android.support.annotation.VisibleForTesting;
42import android.support.v7.preference.Preference;
43import android.support.v7.preference.PreferenceCategory;
44import android.support.v7.preference.PreferenceManager;
45import android.text.TextUtils;
46import android.util.Log;
47import android.view.ContextMenu;
48import android.view.ContextMenu.ContextMenuInfo;
49import android.view.Menu;
50import android.view.MenuItem;
51import android.view.View;
52import android.widget.ProgressBar;
53import android.widget.Toast;
54
55import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
56import com.android.settings.LinkifyUtils;
57import com.android.settings.R;
58import com.android.settings.RestrictedSettingsFragment;
59import com.android.settings.SettingsActivity;
60import com.android.settings.dashboard.SummaryLoader;
61import com.android.settings.location.ScanningSettings;
62import com.android.settings.search.BaseSearchIndexProvider;
63import com.android.settings.search.Indexable;
64import com.android.settings.search.SearchIndexableRaw;
65import com.android.settings.widget.SummaryUpdater.OnSummaryChangeListener;
66import com.android.settings.widget.SwitchBarController;
67import com.android.settings.wifi.details.WifiNetworkDetailsFragment;
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.WifiTracker;
73
74import java.util.ArrayList;
75import java.util.List;
76
77import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
78
79/**
80 * Two types of UI are provided here.
81 *
82 * The first is for "usual Settings", appearing as any other Setup fragment.
83 *
84 * The second is for Setup Wizard, with a simplified interface that hides the action bar
85 * and menus.
86 */
87public class WifiSettings extends RestrictedSettingsFragment
88        implements Indexable, WifiTracker.WifiListener, AccessPointListener,
89        WifiDialog.WifiDialogListener {
90
91    private static final String TAG = "WifiSettings";
92
93    /* package */ static final int MENU_ID_WPS_PBC = Menu.FIRST;
94    private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1;
95    private static final int MENU_ID_CONNECT = Menu.FIRST + 6;
96    private static final int MENU_ID_FORGET = Menu.FIRST + 7;
97    private static final int MENU_ID_MODIFY = Menu.FIRST + 8;
98    private static final int MENU_ID_WRITE_NFC = Menu.FIRST + 9;
99
100    public static final int WIFI_DIALOG_ID = 1;
101    /* package */ static final int WPS_PBC_DIALOG_ID = 2;
102    private static final int WPS_PIN_DIALOG_ID = 3;
103    private static final int WRITE_NFC_DIALOG_ID = 6;
104
105    // Instance state keys
106    private static final String SAVE_DIALOG_MODE = "dialog_mode";
107    private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
108    private static final String SAVED_WIFI_NFC_DIALOG_STATE = "wifi_nfc_dlg_state";
109
110    private static final String PREF_KEY_EMPTY_WIFI_LIST = "wifi_empty_list";
111    private static final String PREF_KEY_CONNECTED_ACCESS_POINTS = "connected_access_point";
112    private static final String PREF_KEY_ACCESS_POINTS = "access_points";
113    private static final String PREF_KEY_ADDITIONAL_SETTINGS = "additional_settings";
114    private static final String PREF_KEY_CONFIGURE_WIFI_SETTINGS = "configure_settings";
115    private static final String PREF_KEY_SAVED_NETWORKS = "saved_networks";
116
117    protected WifiManager mWifiManager;
118    private WifiManager.ActionListener mConnectListener;
119    private WifiManager.ActionListener mSaveListener;
120    private WifiManager.ActionListener mForgetListener;
121
122    private WifiEnabler mWifiEnabler;
123    // An access point being editted is stored here.
124    private AccessPoint mSelectedAccessPoint;
125
126    private WifiDialog mDialog;
127    private WriteWifiConfigToNfcDialog mWifiToNfcDialog;
128
129    private ProgressBar mProgressHeader;
130
131    // this boolean extra specifies whether to disable the Next button when not connected. Used by
132    // account creation outside of setup wizard.
133    private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
134    // This string extra specifies a network to open the connect dialog on, so the user can enter
135    // network credentials.  This is used by quick settings for secured networks.
136    private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
137
138    // should Next button only be enabled when we have a connection?
139    private boolean mEnableNextOnConnection;
140
141    // should see all networks instead of collapsing networks and showing mSeeAllNetworksPreference.
142    private boolean mSeeAllNetworks;
143    private static final int NETWORKS_TO_INITIALLY_SHOW = 5;
144
145    // Save the dialog details
146    private int mDialogMode;
147    private AccessPoint mDlgAccessPoint;
148    private Bundle mAccessPointSavedState;
149    private Bundle mWifiNfcDialogSavedState;
150
151    private WifiTracker mWifiTracker;
152    private String mOpenSsid;
153
154    private HandlerThread mBgThread;
155
156    private AccessPointPreference.UserBadgeCache mUserBadgeCache;
157
158    private PreferenceCategory mConnectedAccessPointPreferenceCategory;
159    private PreferenceCategory mAccessPointsPreferenceCategory;
160    private PreferenceCategory mAdditionalSettingsPreferenceCategory;
161    private Preference mAddPreference;
162    private Preference mSeeAllNetworksPreference;
163    private Preference mConfigureWifiSettingsPreference;
164    private Preference mSavedNetworksPreference;
165    private LinkablePreference mStatusMessagePreference;
166
167    // For Search
168    private static final String DATA_KEY_REFERENCE = "main_toggle_wifi";
169
170    /* End of "used in Wifi Setup context" */
171
172    public WifiSettings() {
173        super(DISALLOW_CONFIG_WIFI);
174    }
175
176    @Override
177    public void onViewCreated(View view, Bundle savedInstanceState) {
178        super.onViewCreated(view, savedInstanceState);
179        final Activity activity = getActivity();
180        if (activity != null) {
181            mProgressHeader = (ProgressBar) setPinnedHeaderView(R.layout.wifi_progress_header);
182            setProgressBarVisible(false);
183        }
184    }
185
186    @Override
187    public void onCreate(Bundle icicle) {
188        super.onCreate(icicle);
189
190        getPreferenceManager().setPreferenceComparisonCallback(
191                new PreferenceManager.SimplePreferenceComparisonCallback());
192        addPreferencesFromResource(R.xml.wifi_settings);
193
194        mConnectedAccessPointPreferenceCategory =
195                (PreferenceCategory) findPreference(PREF_KEY_CONNECTED_ACCESS_POINTS);
196
197        mAccessPointsPreferenceCategory =
198                (PreferenceCategory) findPreference(PREF_KEY_ACCESS_POINTS);
199        mAdditionalSettingsPreferenceCategory =
200                (PreferenceCategory) findPreference(PREF_KEY_ADDITIONAL_SETTINGS);
201        mConfigureWifiSettingsPreference = findPreference(PREF_KEY_CONFIGURE_WIFI_SETTINGS);
202        mSavedNetworksPreference = findPreference(PREF_KEY_SAVED_NETWORKS);
203
204        if (isUiRestricted()) {
205            getPreferenceScreen().removePreference(mAdditionalSettingsPreferenceCategory);
206        }
207
208        Context prefContext = getPrefContext();
209        mAddPreference = new Preference(prefContext);
210        mAddPreference.setIcon(R.drawable.ic_menu_add_inset);
211        mAddPreference.setTitle(R.string.wifi_add_network);
212        mSeeAllNetworksPreference = new Preference(prefContext);
213        mSeeAllNetworksPreference.setIcon(R.drawable.ic_arrow_down_24dp);
214        mSeeAllNetworksPreference.setTitle(R.string.wifi_see_all_networks_button_title);
215        mSeeAllNetworks = false;
216        mStatusMessagePreference = new LinkablePreference(prefContext);
217
218        mUserBadgeCache = new AccessPointPreference.UserBadgeCache(getPackageManager());
219
220        mBgThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
221        mBgThread.start();
222    }
223
224    @Override
225    public void onDestroy() {
226        mBgThread.quit();
227        super.onDestroy();
228    }
229
230    @Override
231    public void onActivityCreated(Bundle savedInstanceState) {
232        super.onActivityCreated(savedInstanceState);
233
234        mWifiTracker =
235                new WifiTracker(getActivity(), this, mBgThread.getLooper(), true, true, false);
236        mWifiManager = mWifiTracker.getManager();
237
238        mConnectListener = new WifiManager.ActionListener() {
239                                   @Override
240                                   public void onSuccess() {
241                                   }
242                                   @Override
243                                   public void onFailure(int reason) {
244                                       Activity activity = getActivity();
245                                       if (activity != null) {
246                                           Toast.makeText(activity,
247                                                R.string.wifi_failed_connect_message,
248                                                Toast.LENGTH_SHORT).show();
249                                       }
250                                   }
251                               };
252
253        mSaveListener = new WifiManager.ActionListener() {
254                                @Override
255                                public void onSuccess() {
256                                }
257                                @Override
258                                public void onFailure(int reason) {
259                                    Activity activity = getActivity();
260                                    if (activity != null) {
261                                        Toast.makeText(activity,
262                                            R.string.wifi_failed_save_message,
263                                            Toast.LENGTH_SHORT).show();
264                                    }
265                                }
266                            };
267
268        mForgetListener = new WifiManager.ActionListener() {
269                                   @Override
270                                   public void onSuccess() {
271                                   }
272                                   @Override
273                                   public void onFailure(int reason) {
274                                       Activity activity = getActivity();
275                                       if (activity != null) {
276                                           Toast.makeText(activity,
277                                               R.string.wifi_failed_forget_message,
278                                               Toast.LENGTH_SHORT).show();
279                                       }
280                                   }
281                               };
282
283        if (savedInstanceState != null) {
284            mDialogMode = savedInstanceState.getInt(SAVE_DIALOG_MODE);
285            if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
286                mAccessPointSavedState =
287                    savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
288            }
289
290            if (savedInstanceState.containsKey(SAVED_WIFI_NFC_DIALOG_STATE)) {
291                mWifiNfcDialogSavedState =
292                    savedInstanceState.getBundle(SAVED_WIFI_NFC_DIALOG_STATE);
293            }
294        }
295
296        // if we're supposed to enable/disable the Next button based on our current connection
297        // state, start it off in the right state
298        Intent intent = getActivity().getIntent();
299        mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
300
301        if (mEnableNextOnConnection) {
302            if (hasNextButton()) {
303                final ConnectivityManager connectivity = (ConnectivityManager)
304                        getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
305                if (connectivity != null) {
306                    NetworkInfo info = connectivity.getNetworkInfo(
307                            ConnectivityManager.TYPE_WIFI);
308                    changeNextButtonState(info.isConnected());
309                }
310            }
311        }
312
313        registerForContextMenu(getListView());
314        setHasOptionsMenu(true);
315
316        if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) {
317            mOpenSsid = intent.getStringExtra(EXTRA_START_CONNECT_SSID);
318            onAccessPointsChanged();
319        }
320    }
321
322    @Override
323    public void onDestroyView() {
324        super.onDestroyView();
325
326        if (mWifiEnabler != null) {
327            mWifiEnabler.teardownSwitchController();
328        }
329    }
330
331    @Override
332    public void onStart() {
333        super.onStart();
334
335        // On/off switch is hidden for Setup Wizard (returns null)
336        mWifiEnabler = createWifiEnabler();
337    }
338
339    /**
340     * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard)
341     */
342    private WifiEnabler createWifiEnabler() {
343        final SettingsActivity activity = (SettingsActivity) getActivity();
344        return new WifiEnabler(activity, new SwitchBarController(activity.getSwitchBar()),
345            mMetricsFeatureProvider);
346    }
347
348    @Override
349    public void onResume() {
350        final Activity activity = getActivity();
351        super.onResume();
352        if (mWifiEnabler != null) {
353            mWifiEnabler.resume(activity);
354        }
355
356        mWifiTracker.startTracking();
357        activity.invalidateOptionsMenu();
358    }
359
360    @Override
361    public void onPause() {
362        super.onPause();
363        if (mWifiEnabler != null) {
364            mWifiEnabler.pause();
365        }
366
367        mWifiTracker.stopTracking();
368    }
369
370    @Override
371    public int getMetricsCategory() {
372        return MetricsEvent.WIFI;
373    }
374
375    @Override
376    public void onSaveInstanceState(Bundle outState) {
377        super.onSaveInstanceState(outState);
378
379        // If the dialog is showing, save its state.
380        if (mDialog != null && mDialog.isShowing()) {
381            outState.putInt(SAVE_DIALOG_MODE, mDialogMode);
382            if (mDlgAccessPoint != null) {
383                mAccessPointSavedState = new Bundle();
384                mDlgAccessPoint.saveWifiState(mAccessPointSavedState);
385                outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
386            }
387        }
388
389        if (mWifiToNfcDialog != null && mWifiToNfcDialog.isShowing()) {
390            Bundle savedState = new Bundle();
391            mWifiToNfcDialog.saveState(savedState);
392            outState.putBundle(SAVED_WIFI_NFC_DIALOG_STATE, savedState);
393        }
394    }
395
396    @Override
397    public boolean onOptionsItemSelected(MenuItem item) {
398        // If the user is not allowed to configure wifi, do not handle menu selections.
399        if (isUiRestricted()) return false;
400
401        switch (item.getItemId()) {
402            case MENU_ID_WPS_PBC:
403                showDialog(WPS_PBC_DIALOG_ID);
404                return true;
405                /*
406            case MENU_ID_P2P:
407                if (getActivity() instanceof SettingsActivity) {
408                    ((SettingsActivity) getActivity()).startPreferencePanel(
409                            WifiP2pSettings.class.getCanonicalName(),
410                            null,
411                            R.string.wifi_p2p_settings_title, null,
412                            this, 0);
413                } else {
414                    startFragment(this, WifiP2pSettings.class.getCanonicalName(),
415                            R.string.wifi_p2p_settings_title, -1, null);
416                }
417                return true;
418                */
419            case MENU_ID_WPS_PIN:
420                showDialog(WPS_PIN_DIALOG_ID);
421                return true;
422        }
423        return super.onOptionsItemSelected(item);
424    }
425
426    @Override
427    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
428            Preference preference = (Preference) view.getTag();
429
430            if (preference instanceof LongPressAccessPointPreference) {
431                mSelectedAccessPoint =
432                        ((LongPressAccessPointPreference) preference).getAccessPoint();
433                menu.setHeaderTitle(mSelectedAccessPoint.getSsid());
434                if (mSelectedAccessPoint.isConnectable()) {
435                    menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
436                }
437
438                WifiConfiguration config = mSelectedAccessPoint.getConfig();
439                // Some configs are ineditable
440                if (isEditabilityLockedDown(getActivity(), config)) {
441                    return;
442                }
443
444                if (mSelectedAccessPoint.isSaved() || mSelectedAccessPoint.isEphemeral()) {
445                    // Allow forgetting a network if either the network is saved or ephemerally
446                    // connected. (In the latter case, "forget" blacklists the network so it won't
447                    // be used again, ephemerally).
448                    menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
449                }
450                if (mSelectedAccessPoint.isSaved()) {
451                    menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
452                    NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
453                    if (nfcAdapter != null && nfcAdapter.isEnabled() &&
454                            mSelectedAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
455                        // Only allow writing of NFC tags for password-protected networks.
456                        menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc);
457                    }
458                }
459            }
460    }
461
462    @Override
463    public boolean onContextItemSelected(MenuItem item) {
464        if (mSelectedAccessPoint == null) {
465            return super.onContextItemSelected(item);
466        }
467        switch (item.getItemId()) {
468            case MENU_ID_CONNECT: {
469                boolean isSavedNetwork = mSelectedAccessPoint.isSaved();
470                if (isSavedNetwork) {
471                    connect(mSelectedAccessPoint.getConfig(), isSavedNetwork);
472                } else if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE) {
473                    /** Bypass dialog for unsecured networks */
474                    mSelectedAccessPoint.generateOpenNetworkConfig();
475                    connect(mSelectedAccessPoint.getConfig(), isSavedNetwork);
476                } else {
477                    showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_CONNECT);
478                }
479                return true;
480            }
481            case MENU_ID_FORGET: {
482                forget();
483                return true;
484            }
485            case MENU_ID_MODIFY: {
486                showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_MODIFY);
487                return true;
488            }
489            case MENU_ID_WRITE_NFC:
490                showDialog(WRITE_NFC_DIALOG_ID);
491                return true;
492
493        }
494        return super.onContextItemSelected(item);
495    }
496
497    @Override
498    public boolean onPreferenceTreeClick(Preference preference) {
499        // If the preference has a fragment set, open that
500        if (preference.getFragment() != null) {
501            preference.setOnPreferenceClickListener(null);
502            return super.onPreferenceTreeClick(preference);
503        }
504
505        if (preference instanceof LongPressAccessPointPreference) {
506            mSelectedAccessPoint = ((LongPressAccessPointPreference) preference).getAccessPoint();
507            if (mSelectedAccessPoint == null) {
508                return false;
509            }
510            /** Bypass dialog for unsecured, unsaved, and inactive networks */
511            if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE &&
512                    !mSelectedAccessPoint.isSaved() && !mSelectedAccessPoint.isActive()) {
513                mSelectedAccessPoint.generateOpenNetworkConfig();
514                connect(mSelectedAccessPoint.getConfig(), false /* isSavedNetwork */);
515            } else if (mSelectedAccessPoint.isSaved()) {
516                showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_VIEW);
517            } else {
518                showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_CONNECT);
519            }
520        } else if (preference == mAddPreference) {
521            onAddNetworkPressed();
522        } else if (preference == mSeeAllNetworksPreference) {
523            mSeeAllNetworks = true;
524            onAccessPointsChanged();
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    @Override
593    public int getDialogMetricsCategory(int dialogId) {
594        switch (dialogId) {
595            case WIFI_DIALOG_ID:
596                return MetricsEvent.DIALOG_WIFI_AP_EDIT;
597            case WPS_PBC_DIALOG_ID:
598                return MetricsEvent.DIALOG_WIFI_PBC;
599            case WPS_PIN_DIALOG_ID:
600                return MetricsEvent.DIALOG_WIFI_PIN;
601            case WRITE_NFC_DIALOG_ID:
602                return MetricsEvent.DIALOG_WIFI_WRITE_NFC;
603            default:
604                return 0;
605        }
606    }
607
608    /**
609     * Shows the latest access points available with supplemental information like
610     * the strength of network and the security for it.
611     */
612    @Override
613    public void onAccessPointsChanged() {
614        // Safeguard from some delayed event handling
615        if (getActivity() == null) return;
616        final int wifiState = mWifiManager.getWifiState();
617        if (isUiRestricted()) {
618            removeConnectedAccessPointPreference();
619            mAccessPointsPreferenceCategory.removeAll();
620            if (!isUiRestrictedByOnlyAdmin()) {
621                if (wifiState == WifiManager.WIFI_AP_STATE_DISABLED) {
622                    setOffMessage();
623                } else {
624                    addMessagePreference(R.string.wifi_empty_list_user_restricted);
625                }
626            }
627            return;
628        }
629
630        switch (wifiState) {
631            case WifiManager.WIFI_STATE_ENABLED:
632                setProgressBarVisible(true);
633                // Have the progress bar displayed before starting to modify APs
634                getView().postDelayed(() -> {
635                        updateAccessPointPreferences();
636                    }, 300 /* delay milliseconds */);
637                break;
638
639            case WifiManager.WIFI_STATE_ENABLING:
640                removeConnectedAccessPointPreference();
641                mAccessPointsPreferenceCategory.removeAll();
642                setProgressBarVisible(true);
643                break;
644
645            case WifiManager.WIFI_STATE_DISABLING:
646                addMessagePreference(R.string.wifi_stopping);
647                setProgressBarVisible(true);
648                break;
649
650            case WifiManager.WIFI_STATE_DISABLED:
651                setOffMessage();
652                setConfigureWifiSettingsVisibility();
653                setProgressBarVisible(false);
654                break;
655        }
656    }
657
658    private void updateAccessPointPreferences() {
659        // AccessPoints are sorted by the WifiTracker
660        final List<AccessPoint> accessPoints = mWifiTracker.getAccessPoints();
661
662        boolean hasAvailableAccessPoints = false;
663        mAccessPointsPreferenceCategory.removePreference(mStatusMessagePreference);
664        cacheRemoveAllPrefs(mAccessPointsPreferenceCategory);
665
666        int index =
667                configureConnectedAccessPointPreferenceCategory(accessPoints) ? 1 : 0;
668        boolean fewerNetworksThanLimit =
669                accessPoints.size() <= index + NETWORKS_TO_INITIALLY_SHOW;
670        int numAccessPointsToShow = mSeeAllNetworks || fewerNetworksThanLimit
671                ? accessPoints.size() : index + NETWORKS_TO_INITIALLY_SHOW;
672
673        for (; index < numAccessPointsToShow; index++) {
674            AccessPoint accessPoint = accessPoints.get(index);
675            // Ignore access points that are out of range.
676            if (accessPoint.isReachable()) {
677                String key = accessPoint.getBssid();
678                if (TextUtils.isEmpty(key)) {
679                    key = accessPoint.getSsidStr();
680                }
681                hasAvailableAccessPoints = true;
682                LongPressAccessPointPreference pref =
683                        (LongPressAccessPointPreference) getCachedPreference(key);
684                if (pref != null) {
685                    pref.setOrder(index);
686                    continue;
687                }
688                LongPressAccessPointPreference preference =
689                        createLongPressActionPointPreference(accessPoint);
690                preference.setKey(key);
691                preference.setOrder(index);
692                if (mOpenSsid != null && mOpenSsid.equals(accessPoint.getSsidStr())
693                        && !accessPoint.isSaved()
694                        && accessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
695                    onPreferenceTreeClick(preference);
696                    mOpenSsid = null;
697                }
698                mAccessPointsPreferenceCategory.addPreference(preference);
699                accessPoint.setListener(WifiSettings.this);
700                preference.refresh();
701            }
702        }
703        removeCachedPrefs(mAccessPointsPreferenceCategory);
704        if (mSeeAllNetworks || fewerNetworksThanLimit) {
705            mAccessPointsPreferenceCategory.removePreference(mSeeAllNetworksPreference);
706            mAddPreference.setOrder(index);
707            mAccessPointsPreferenceCategory.addPreference(mAddPreference);
708        } else {
709            mAccessPointsPreferenceCategory.removePreference(mAddPreference);
710            mSeeAllNetworksPreference.setOrder(index);
711            mAccessPointsPreferenceCategory.addPreference(mSeeAllNetworksPreference);
712        }
713        setConfigureWifiSettingsVisibility();
714
715        if (!hasAvailableAccessPoints) {
716            setProgressBarVisible(true);
717            Preference pref = new Preference(getPrefContext());
718            pref.setSelectable(false);
719            pref.setSummary(R.string.wifi_empty_list_wifi_on);
720            pref.setOrder(index++);
721            pref.setKey(PREF_KEY_EMPTY_WIFI_LIST);
722            mAccessPointsPreferenceCategory.addPreference(pref);
723        } else {
724            // Continuing showing progress bar for an additional delay to overlap with animation
725            getView().postDelayed(() -> {
726                    setProgressBarVisible(false);
727                }, 1700 /* delay millis */);
728        }
729    }
730
731    @NonNull
732    private LongPressAccessPointPreference createLongPressActionPointPreference(
733            AccessPoint accessPoint) {
734        return new LongPressAccessPointPreference(accessPoint, getPrefContext(), mUserBadgeCache,
735                false, R.drawable.ic_wifi_signal_0, this);
736    }
737
738    /**
739     * Configure the ConnectedAccessPointPreferenceCategory and return true if the Category was
740     * shown.
741     */
742    private boolean configureConnectedAccessPointPreferenceCategory(
743            List<AccessPoint> accessPoints) {
744        if (accessPoints.size() == 0) {
745            removeConnectedAccessPointPreference();
746            return false;
747        }
748
749        AccessPoint connectedAp = accessPoints.get(0);
750        if (!connectedAp.isActive()) {
751            removeConnectedAccessPointPreference();
752            return false;
753        }
754
755        // Is the preference category empty?
756        if (mConnectedAccessPointPreferenceCategory.getPreferenceCount() == 0) {
757            addConnectedAccessPointPreference(connectedAp);
758            return true;
759        }
760
761        // Is the previous currently connected SSID different from the new one?
762        if (!((AccessPointPreference)
763                mConnectedAccessPointPreferenceCategory.getPreference(0))
764                        .getAccessPoint().getSsidStr().equals(
765                                connectedAp.getSsidStr())) {
766            removeConnectedAccessPointPreference();
767            addConnectedAccessPointPreference(connectedAp);
768            return true;
769        }
770
771        // Else same AP is connected, simply refresh the connected access point preference
772        // (first and only access point in this category).
773        ((LongPressAccessPointPreference) mConnectedAccessPointPreferenceCategory.getPreference(0))
774                .refresh();
775        return true;
776    }
777
778    /**
779     * Creates a Preference for the given {@link AccessPoint} and adds it to the
780     * {@link #mConnectedAccessPointPreferenceCategory}.
781     */
782    private void addConnectedAccessPointPreference(AccessPoint connectedAp) {
783        String key = connectedAp.getBssid();
784        LongPressAccessPointPreference pref = (LongPressAccessPointPreference)
785                getCachedPreference(key);
786        if (pref == null) {
787            pref = createLongPressActionPointPreference(connectedAp);
788        }
789
790        // Save the state of the current access point in the bundle so that we can restore it
791        // in the Wifi Network Details Fragment
792        pref.getAccessPoint().saveWifiState(pref.getExtras());
793        pref.setFragment(WifiNetworkDetailsFragment.class.getName());
794
795        pref.refresh();
796        mConnectedAccessPointPreferenceCategory.addPreference(pref);
797        mConnectedAccessPointPreferenceCategory.setVisible(true);
798    }
799
800    /** Removes all preferences and hide the {@link #mConnectedAccessPointPreferenceCategory}. */
801    private void removeConnectedAccessPointPreference() {
802        mConnectedAccessPointPreferenceCategory.removeAll();
803        mConnectedAccessPointPreferenceCategory.setVisible(false);
804    }
805
806    private void setConfigureWifiSettingsVisibility() {
807        if (isUiRestricted()) {
808            mAdditionalSettingsPreferenceCategory.removeAll();
809            return;
810        }
811        mAdditionalSettingsPreferenceCategory.addPreference(mConfigureWifiSettingsPreference);
812        if (mWifiTracker.doSavedNetworksExist()) {
813            mAdditionalSettingsPreferenceCategory.addPreference(mSavedNetworksPreference);
814        } else {
815            mAdditionalSettingsPreferenceCategory.removePreference(mSavedNetworksPreference);
816        }
817    }
818
819    private void setOffMessage() {
820        final CharSequence briefText = getText(R.string.wifi_empty_list_wifi_off);
821
822        // Don't use WifiManager.isScanAlwaysAvailable() to check the Wi-Fi scanning mode. Instead,
823        // read the system settings directly. Because when the device is in Airplane mode, even if
824        // Wi-Fi scanning mode is on, WifiManager.isScanAlwaysAvailable() still returns "off".
825        final ContentResolver resolver = getActivity().getContentResolver();
826        final boolean wifiScanningMode = !isUiRestricted() && Settings.Global.getInt(
827                resolver, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1;
828
829        if (!wifiScanningMode) {
830            // Show only the brief text if the user is not allowed to configure scanning settings,
831            // or the scanning mode has been turned off.
832            mStatusMessagePreference.setTitle(briefText);
833        } else {
834            LinkifyUtils.OnClickListener clickListener = new LinkifyUtils.OnClickListener() {
835                @Override
836                public void onClick() {
837                    final SettingsActivity activity = (SettingsActivity) getActivity();
838                    activity.startPreferencePanel(WifiSettings.this,
839                            ScanningSettings.class.getName(),
840                            null, R.string.location_scanning_screen_title, null, null, 0);
841                }
842            };
843            mStatusMessagePreference.setText(
844                    briefText, getText(R.string.wifi_scan_notify_text), clickListener);
845        }
846        removeConnectedAccessPointPreference();
847        mAccessPointsPreferenceCategory.removeAll();
848        mAccessPointsPreferenceCategory.addPreference(mStatusMessagePreference);
849    }
850
851    private void addMessagePreference(int messageId) {
852        mStatusMessagePreference.setTitle(messageId);
853        removeConnectedAccessPointPreference();
854        mAccessPointsPreferenceCategory.removeAll();
855        mAccessPointsPreferenceCategory.addPreference(mStatusMessagePreference);
856    }
857
858    protected void setProgressBarVisible(boolean visible) {
859        if (mProgressHeader != null) {
860            mProgressHeader.setVisibility(
861                    visible && !isUiRestricted() ? View.VISIBLE : View.INVISIBLE);
862        }
863    }
864
865    @Override
866    public void onWifiStateChanged(int state) {
867        switch (state) {
868            case WifiManager.WIFI_STATE_ENABLING:
869                addMessagePreference(R.string.wifi_starting);
870                setProgressBarVisible(true);
871                break;
872
873            case WifiManager.WIFI_STATE_DISABLED:
874                setOffMessage();
875                setProgressBarVisible(false);
876                break;
877        }
878    }
879
880    @Override
881    public void onConnectedChanged() {
882        onAccessPointsChanged();
883        changeNextButtonState(mWifiTracker.isConnected());
884    }
885
886    /**
887     * Renames/replaces "Next" button when appropriate. "Next" button usually exists in
888     * Wifi setup screens, not in usual wifi settings screen.
889     *
890     * @param enabled true when the device is connected to a wifi network.
891     */
892    private void changeNextButtonState(boolean enabled) {
893        if (mEnableNextOnConnection && hasNextButton()) {
894            getNextButton().setEnabled(enabled);
895        }
896    }
897
898    @Override
899    public void onForget(WifiDialog dialog) {
900        forget();
901    }
902
903    @Override
904    public void onSubmit(WifiDialog dialog) {
905        if (mDialog != null) {
906            submit(mDialog.getController());
907        }
908    }
909
910    /* package */ void submit(WifiConfigController configController) {
911
912        final WifiConfiguration config = configController.getConfig();
913
914        if (config == null) {
915            if (mSelectedAccessPoint != null
916                    && mSelectedAccessPoint.isSaved()) {
917                connect(mSelectedAccessPoint.getConfig(), true /* isSavedNetwork */);
918            }
919        } else if (configController.getMode() == WifiConfigUiBase.MODE_MODIFY) {
920            mWifiManager.save(config, mSaveListener);
921        } else {
922            mWifiManager.save(config, mSaveListener);
923            if (mSelectedAccessPoint != null) { // Not an "Add network"
924                connect(config, false /* isSavedNetwork */);
925            }
926        }
927
928        mWifiTracker.resumeScanning();
929    }
930
931    /* package */ void forget() {
932        mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_FORGET);
933        if (!mSelectedAccessPoint.isSaved()) {
934            if (mSelectedAccessPoint.getNetworkInfo() != null &&
935                    mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED) {
936                // Network is active but has no network ID - must be ephemeral.
937                mWifiManager.disableEphemeralNetwork(
938                        AccessPoint.convertToQuotedString(mSelectedAccessPoint.getSsidStr()));
939            } else {
940                // Should not happen, but a monkey seems to trigger it
941                Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig());
942                return;
943            }
944        } else if (mSelectedAccessPoint.getConfig().isPasspoint()) {
945            mWifiManager.removePasspointConfiguration(mSelectedAccessPoint.getConfig().FQDN);
946        } else {
947            mWifiManager.forget(mSelectedAccessPoint.getConfig().networkId, mForgetListener);
948        }
949
950        mWifiTracker.resumeScanning();
951
952        // We need to rename/replace "Next" button in wifi setup context.
953        changeNextButtonState(false);
954    }
955
956    protected void connect(final WifiConfiguration config, boolean isSavedNetwork) {
957        // Log subtype if configuration is a saved network.
958        mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT,
959                isSavedNetwork);
960        mWifiManager.connect(config, mConnectListener);
961    }
962
963    protected void connect(final int networkId, boolean isSavedNetwork) {
964        // Log subtype if configuration is a saved network.
965        mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT,
966                isSavedNetwork);
967        mWifiManager.connect(networkId, mConnectListener);
968    }
969
970    /**
971     * Called when "add network" button is pressed.
972     */
973    /* package */ void onAddNetworkPressed() {
974        mMetricsFeatureProvider.action(getActivity(), MetricsEvent.ACTION_WIFI_ADD_NETWORK);
975        // No exact access point is selected.
976        mSelectedAccessPoint = null;
977        showDialog(null, WifiConfigUiBase.MODE_CONNECT);
978    }
979
980    @Override
981    protected int getHelpResource() {
982        return R.string.help_url_wifi;
983    }
984
985    @Override
986    public void onAccessPointChanged(final AccessPoint accessPoint) {
987        View view = getView();
988        if (view != null) {
989            view.post(new Runnable() {
990                @Override
991                public void run() {
992                    Object tag = accessPoint.getTag();
993                    if (tag != null) {
994                        ((LongPressAccessPointPreference) tag).refresh();
995                    }
996                }
997            });
998        }
999    }
1000
1001    @Override
1002    public void onLevelChanged(AccessPoint accessPoint) {
1003        ((LongPressAccessPointPreference) accessPoint.getTag()).onLevelChanged();
1004    }
1005
1006    public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
1007        new BaseSearchIndexProvider() {
1008            @Override
1009            public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
1010                final List<SearchIndexableRaw> result = new ArrayList<>();
1011                final Resources res = context.getResources();
1012
1013                // Add fragment title
1014                SearchIndexableRaw data = new SearchIndexableRaw(context);
1015                data.title = res.getString(R.string.wifi_settings);
1016                data.screenTitle = res.getString(R.string.wifi_settings);
1017                data.keywords = res.getString(R.string.keywords_wifi);
1018                data.key = DATA_KEY_REFERENCE;
1019                result.add(data);
1020
1021                // Add saved Wi-Fi access points
1022                final List<AccessPoint> accessPoints =
1023                        WifiTracker.getCurrentAccessPoints(context, true, false, false);
1024                for (AccessPoint accessPoint : accessPoints) {
1025                    data = new SearchIndexableRaw(context);
1026                    data.title = accessPoint.getSsidStr();
1027                    data.screenTitle = res.getString(R.string.wifi_settings);
1028                    data.enabled = enabled;
1029                    result.add(data);
1030                }
1031
1032                return result;
1033            }
1034        };
1035
1036    /**
1037     * Returns true if the config is not editable through Settings.
1038     * @param context Context of caller
1039     * @param config The WiFi config.
1040     * @return true if the config is not editable through Settings.
1041     */
1042    static boolean isEditabilityLockedDown(Context context, WifiConfiguration config) {
1043        return !canModifyNetwork(context, config);
1044    }
1045
1046    /**
1047     * This method is a stripped version of WifiConfigStore.canModifyNetwork.
1048     * TODO: refactor to have only one method.
1049     * @param context Context of caller
1050     * @param config The WiFi config.
1051     * @return true if Settings can modify the config.
1052     */
1053    static boolean canModifyNetwork(Context context, WifiConfiguration config) {
1054        if (config == null) {
1055            return true;
1056        }
1057
1058        final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
1059                Context.DEVICE_POLICY_SERVICE);
1060
1061        // Check if device has DPM capability. If it has and dpm is still null, then we
1062        // treat this case with suspicion and bail out.
1063        final PackageManager pm = context.getPackageManager();
1064        if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) && dpm == null) {
1065            return false;
1066        }
1067
1068        boolean isConfigEligibleForLockdown = false;
1069        if (dpm != null) {
1070            final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnAnyUser();
1071            if (deviceOwner != null) {
1072                final int deviceOwnerUserId = dpm.getDeviceOwnerUserId();
1073                try {
1074                    final int deviceOwnerUid = pm.getPackageUidAsUser(deviceOwner.getPackageName(),
1075                            deviceOwnerUserId);
1076                    isConfigEligibleForLockdown = deviceOwnerUid == config.creatorUid;
1077                } catch (NameNotFoundException e) {
1078                    // don't care
1079                }
1080            }
1081        }
1082        if (!isConfigEligibleForLockdown) {
1083            return true;
1084        }
1085
1086        final ContentResolver resolver = context.getContentResolver();
1087        final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
1088                Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
1089        return !isLockdownFeatureEnabled;
1090    }
1091
1092    private static class SummaryProvider
1093            implements SummaryLoader.SummaryProvider, OnSummaryChangeListener {
1094
1095        private final Context mContext;
1096        private final SummaryLoader mSummaryLoader;
1097
1098        @VisibleForTesting
1099        WifiSummaryUpdater mSummaryHelper;
1100
1101        public SummaryProvider(Context context, SummaryLoader summaryLoader) {
1102            mContext = context;
1103            mSummaryLoader = summaryLoader;
1104            mSummaryHelper = new WifiSummaryUpdater(mContext, this);
1105        }
1106
1107
1108        @Override
1109        public void setListening(boolean listening) {
1110            mSummaryHelper.register(listening);
1111        }
1112
1113        @Override
1114        public void onSummaryChanged(String summary) {
1115            mSummaryLoader.setSummary(this, summary);
1116        }
1117    }
1118
1119    public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
1120            = new SummaryLoader.SummaryProviderFactory() {
1121        @Override
1122        public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
1123                                                                   SummaryLoader summaryLoader) {
1124            return new SummaryProvider(activity, summaryLoader);
1125        }
1126    };
1127}
1128