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