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