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