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