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 
17 package com.android.settings.wifi;
18 
19 import android.app.Activity;
20 import android.app.Dialog;
21 import android.app.admin.DevicePolicyManager;
22 import android.content.BroadcastReceiver;
23 import android.content.ComponentName;
24 import android.content.ContentResolver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.pm.PackageManager;
29 import android.content.pm.PackageManager.NameNotFoundException;
30 import android.content.res.Resources;
31 import android.net.ConnectivityManager;
32 import android.net.NetworkInfo;
33 import android.net.NetworkInfo.State;
34 import android.net.wifi.WifiConfiguration;
35 import android.net.wifi.WifiManager;
36 import android.net.wifi.WpsInfo;
37 import android.nfc.NfcAdapter;
38 import android.os.Bundle;
39 import android.os.HandlerThread;
40 import android.os.Process;
41 import android.provider.Settings;
42 import android.support.v7.preference.Preference;
43 import android.support.v7.preference.PreferenceViewHolder;
44 import android.text.Spannable;
45 import android.text.style.TextAppearanceSpan;
46 import android.util.Log;
47 import android.view.ContextMenu;
48 import android.view.ContextMenu.ContextMenuInfo;
49 import android.view.Menu;
50 import android.view.MenuInflater;
51 import android.view.MenuItem;
52 import android.view.View;
53 import android.widget.ProgressBar;
54 import android.widget.TextView;
55 import android.widget.TextView.BufferType;
56 import android.widget.Toast;
57 import com.android.internal.logging.MetricsLogger;
58 import com.android.internal.logging.MetricsProto.MetricsEvent;
59 import com.android.settings.LinkifyUtils;
60 import com.android.settings.R;
61 import com.android.settings.RestrictedSettingsFragment;
62 import com.android.settings.SettingsActivity;
63 import com.android.settings.dashboard.SummaryLoader;
64 import com.android.settings.location.ScanningSettings;
65 import com.android.settings.search.BaseSearchIndexProvider;
66 import com.android.settings.search.Indexable;
67 import com.android.settings.search.SearchIndexableRaw;
68 import com.android.settingslib.RestrictedLockUtils;
69 import com.android.settingslib.wifi.AccessPoint;
70 import com.android.settingslib.wifi.AccessPoint.AccessPointListener;
71 import com.android.settingslib.wifi.AccessPointPreference;
72 import com.android.settingslib.wifi.WifiStatusTracker;
73 import com.android.settingslib.wifi.WifiTracker;
74 
75 import java.util.ArrayList;
76 import java.util.Collection;
77 import java.util.List;
78 
79 import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
80 
81 /**
82  * Two types of UI are provided here.
83  *
84  * The first is for "usual Settings", appearing as any other Setup fragment.
85  *
86  * The second is for Setup Wizard, with a simplified interface that hides the action bar
87  * and menus.
88  */
89 public class WifiSettings extends RestrictedSettingsFragment
90         implements Indexable, WifiTracker.WifiListener, AccessPointListener,
91         WifiDialog.WifiDialogListener {
92 
93     private static final String TAG = "WifiSettings";
94 
95     /* package */ static final int MENU_ID_WPS_PBC = Menu.FIRST;
96     private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1;
97     private static final int MENU_ID_ADVANCED = Menu.FIRST + 4;
98     private static final int MENU_ID_SCAN = Menu.FIRST + 5;
99     private static final int MENU_ID_CONNECT = Menu.FIRST + 6;
100     private static final int MENU_ID_FORGET = Menu.FIRST + 7;
101     private static final int MENU_ID_MODIFY = Menu.FIRST + 8;
102     private static final int MENU_ID_WRITE_NFC = Menu.FIRST + 9;
103     private static final int MENU_ID_CONFIGURE = Menu.FIRST + 10;
104 
105     public static final int WIFI_DIALOG_ID = 1;
106     /* package */ static final int WPS_PBC_DIALOG_ID = 2;
107     private static final int WPS_PIN_DIALOG_ID = 3;
108     private static final int WRITE_NFC_DIALOG_ID = 6;
109 
110     // Instance state keys
111     private static final String SAVE_DIALOG_MODE = "dialog_mode";
112     private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state";
113     private static final String SAVED_WIFI_NFC_DIALOG_STATE = "wifi_nfc_dlg_state";
114 
115     protected WifiManager mWifiManager;
116     private WifiManager.ActionListener mConnectListener;
117     private WifiManager.ActionListener mSaveListener;
118     private WifiManager.ActionListener mForgetListener;
119 
120     private WifiEnabler mWifiEnabler;
121     // An access point being editted is stored here.
122     private AccessPoint mSelectedAccessPoint;
123 
124     private WifiDialog mDialog;
125     private WriteWifiConfigToNfcDialog mWifiToNfcDialog;
126 
127     private ProgressBar mProgressHeader;
128 
129     // this boolean extra specifies whether to disable the Next button when not connected. Used by
130     // account creation outside of setup wizard.
131     private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect";
132     // This string extra specifies a network to open the connect dialog on, so the user can enter
133     // network credentials.  This is used by quick settings for secured networks.
134     private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid";
135 
136     // should Next button only be enabled when we have a connection?
137     private boolean mEnableNextOnConnection;
138 
139     // Save the dialog details
140     private int mDialogMode;
141     private AccessPoint mDlgAccessPoint;
142     private Bundle mAccessPointSavedState;
143     private Bundle mWifiNfcDialogSavedState;
144 
145     private WifiTracker mWifiTracker;
146     private String mOpenSsid;
147 
148     private HandlerThread mBgThread;
149 
150     private AccessPointPreference.UserBadgeCache mUserBadgeCache;
151     private Preference mAddPreference;
152 
153     private MenuItem mScanMenuItem;
154 
155     /* End of "used in Wifi Setup context" */
156 
WifiSettings()157     public WifiSettings() {
158         super(DISALLOW_CONFIG_WIFI);
159     }
160 
161     @Override
onViewCreated(View view, Bundle savedInstanceState)162     public void onViewCreated(View view, Bundle savedInstanceState) {
163         super.onViewCreated(view, savedInstanceState);
164         final Activity activity = getActivity();
165         if (activity != null) {
166             mProgressHeader = (ProgressBar) setPinnedHeaderView(R.layout.wifi_progress_header);
167         }
168     }
169 
170     @Override
onCreate(Bundle icicle)171     public void onCreate(Bundle icicle) {
172         super.onCreate(icicle);
173         addPreferencesFromResource(R.xml.wifi_settings);
174         mAddPreference = new Preference(getContext());
175         mAddPreference.setIcon(R.drawable.ic_menu_add_inset);
176         mAddPreference.setTitle(R.string.wifi_add_network);
177 
178         mUserBadgeCache = new AccessPointPreference.UserBadgeCache(getPackageManager());
179 
180         mBgThread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
181         mBgThread.start();
182     }
183 
184     @Override
onDestroy()185     public void onDestroy() {
186         mBgThread.quit();
187         super.onDestroy();
188     }
189 
190     @Override
onActivityCreated(Bundle savedInstanceState)191     public void onActivityCreated(Bundle savedInstanceState) {
192         super.onActivityCreated(savedInstanceState);
193 
194         mWifiTracker =
195                 new WifiTracker(getActivity(), this, mBgThread.getLooper(), true, true, false);
196         mWifiManager = mWifiTracker.getManager();
197 
198         mConnectListener = new WifiManager.ActionListener() {
199                                    @Override
200                                    public void onSuccess() {
201                                    }
202                                    @Override
203                                    public void onFailure(int reason) {
204                                        Activity activity = getActivity();
205                                        if (activity != null) {
206                                            Toast.makeText(activity,
207                                                 R.string.wifi_failed_connect_message,
208                                                 Toast.LENGTH_SHORT).show();
209                                        }
210                                    }
211                                };
212 
213         mSaveListener = new WifiManager.ActionListener() {
214                                 @Override
215                                 public void onSuccess() {
216                                 }
217                                 @Override
218                                 public void onFailure(int reason) {
219                                     Activity activity = getActivity();
220                                     if (activity != null) {
221                                         Toast.makeText(activity,
222                                             R.string.wifi_failed_save_message,
223                                             Toast.LENGTH_SHORT).show();
224                                     }
225                                 }
226                             };
227 
228         mForgetListener = new WifiManager.ActionListener() {
229                                    @Override
230                                    public void onSuccess() {
231                                    }
232                                    @Override
233                                    public void onFailure(int reason) {
234                                        Activity activity = getActivity();
235                                        if (activity != null) {
236                                            Toast.makeText(activity,
237                                                R.string.wifi_failed_forget_message,
238                                                Toast.LENGTH_SHORT).show();
239                                        }
240                                    }
241                                };
242 
243         if (savedInstanceState != null) {
244             mDialogMode = savedInstanceState.getInt(SAVE_DIALOG_MODE);
245             if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) {
246                 mAccessPointSavedState =
247                     savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE);
248             }
249 
250             if (savedInstanceState.containsKey(SAVED_WIFI_NFC_DIALOG_STATE)) {
251                 mWifiNfcDialogSavedState =
252                     savedInstanceState.getBundle(SAVED_WIFI_NFC_DIALOG_STATE);
253             }
254         }
255 
256         // if we're supposed to enable/disable the Next button based on our current connection
257         // state, start it off in the right state
258         Intent intent = getActivity().getIntent();
259         mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false);
260 
261         if (mEnableNextOnConnection) {
262             if (hasNextButton()) {
263                 final ConnectivityManager connectivity = (ConnectivityManager)
264                         getActivity().getSystemService(Context.CONNECTIVITY_SERVICE);
265                 if (connectivity != null) {
266                     NetworkInfo info = connectivity.getNetworkInfo(
267                             ConnectivityManager.TYPE_WIFI);
268                     changeNextButtonState(info.isConnected());
269                 }
270             }
271         }
272 
273         registerForContextMenu(getListView());
274         setHasOptionsMenu(true);
275 
276         if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) {
277             mOpenSsid = intent.getStringExtra(EXTRA_START_CONNECT_SSID);
278             onAccessPointsChanged();
279         }
280     }
281 
282     @Override
onDestroyView()283     public void onDestroyView() {
284         super.onDestroyView();
285 
286         if (mWifiEnabler != null) {
287             mWifiEnabler.teardownSwitchBar();
288         }
289     }
290 
291     @Override
onStart()292     public void onStart() {
293         super.onStart();
294 
295         // On/off switch is hidden for Setup Wizard (returns null)
296         mWifiEnabler = createWifiEnabler();
297     }
298 
299     /**
300      * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard)
301      */
createWifiEnabler()302     /* package */ WifiEnabler createWifiEnabler() {
303         final SettingsActivity activity = (SettingsActivity) getActivity();
304         return new WifiEnabler(activity, activity.getSwitchBar());
305     }
306 
307     @Override
onResume()308     public void onResume() {
309         final Activity activity = getActivity();
310         super.onResume();
311         removePreference("dummy");
312         if (mWifiEnabler != null) {
313             mWifiEnabler.resume(activity);
314         }
315 
316         mWifiTracker.startTracking();
317         activity.invalidateOptionsMenu();
318     }
319 
320     @Override
onPause()321     public void onPause() {
322         super.onPause();
323         if (mWifiEnabler != null) {
324             mWifiEnabler.pause();
325         }
326 
327         mWifiTracker.stopTracking();
328     }
329 
330     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)331     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
332         // If the user is not allowed to configure wifi, do not show the menu.
333         if (isUiRestricted()) return;
334 
335         addOptionsMenuItems(menu);
336         super.onCreateOptionsMenu(menu, inflater);
337     }
338 
339     /**
340      * @param menu
341      */
addOptionsMenuItems(Menu menu)342     void addOptionsMenuItems(Menu menu) {
343         final boolean wifiIsEnabled = mWifiTracker.isWifiEnabled();
344         mScanMenuItem = menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.menu_stats_refresh);
345         mScanMenuItem.setEnabled(wifiIsEnabled)
346                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
347         menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced)
348                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
349         menu.add(Menu.NONE, MENU_ID_CONFIGURE, 0, R.string.wifi_menu_configure)
350                 .setIcon(R.drawable.ic_settings_24dp)
351                 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
352     }
353 
354     @Override
getMetricsCategory()355     protected int getMetricsCategory() {
356         return MetricsEvent.WIFI;
357     }
358 
359     @Override
onSaveInstanceState(Bundle outState)360     public void onSaveInstanceState(Bundle outState) {
361         super.onSaveInstanceState(outState);
362 
363         // If the dialog is showing, save its state.
364         if (mDialog != null && mDialog.isShowing()) {
365             outState.putInt(SAVE_DIALOG_MODE, mDialogMode);
366             if (mDlgAccessPoint != null) {
367                 mAccessPointSavedState = new Bundle();
368                 mDlgAccessPoint.saveWifiState(mAccessPointSavedState);
369                 outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState);
370             }
371         }
372 
373         if (mWifiToNfcDialog != null && mWifiToNfcDialog.isShowing()) {
374             Bundle savedState = new Bundle();
375             mWifiToNfcDialog.saveState(savedState);
376             outState.putBundle(SAVED_WIFI_NFC_DIALOG_STATE, savedState);
377         }
378     }
379 
380     @Override
onOptionsItemSelected(MenuItem item)381     public boolean onOptionsItemSelected(MenuItem item) {
382         // If the user is not allowed to configure wifi, do not handle menu selections.
383         if (isUiRestricted()) return false;
384 
385         switch (item.getItemId()) {
386             case MENU_ID_WPS_PBC:
387                 showDialog(WPS_PBC_DIALOG_ID);
388                 return true;
389                 /*
390             case MENU_ID_P2P:
391                 if (getActivity() instanceof SettingsActivity) {
392                     ((SettingsActivity) getActivity()).startPreferencePanel(
393                             WifiP2pSettings.class.getCanonicalName(),
394                             null,
395                             R.string.wifi_p2p_settings_title, null,
396                             this, 0);
397                 } else {
398                     startFragment(this, WifiP2pSettings.class.getCanonicalName(),
399                             R.string.wifi_p2p_settings_title, -1, null);
400                 }
401                 return true;
402                 */
403             case MENU_ID_WPS_PIN:
404                 showDialog(WPS_PIN_DIALOG_ID);
405                 return true;
406             case MENU_ID_SCAN:
407                 MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_FORCE_SCAN);
408                 mWifiTracker.forceScan();
409                 return true;
410             case MENU_ID_ADVANCED:
411                 if (getActivity() instanceof SettingsActivity) {
412                     ((SettingsActivity) getActivity()).startPreferencePanel(
413                             AdvancedWifiSettings.class.getCanonicalName(), null,
414                             R.string.wifi_advanced_titlebar, null, this, 0);
415                 } else {
416                     startFragment(this, AdvancedWifiSettings.class.getCanonicalName(),
417                             R.string.wifi_advanced_titlebar, -1 /* Do not request a results */,
418                             null);
419                 }
420                 return true;
421             case MENU_ID_CONFIGURE:
422                 if (getActivity() instanceof SettingsActivity) {
423                     ((SettingsActivity) getActivity()).startPreferencePanel(
424                             ConfigureWifiSettings.class.getCanonicalName(), null,
425                             R.string.wifi_configure_titlebar, null, this, 0);
426                 } else {
427                     startFragment(this, ConfigureWifiSettings.class.getCanonicalName(),
428                             R.string.wifi_configure_titlebar, -1 /* Do not request a results */,
429                             null);
430                 }
431                 return true;
432 
433         }
434         return super.onOptionsItemSelected(item);
435     }
436 
437     @Override
onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info)438     public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) {
439             Preference preference = (Preference) view.getTag();
440 
441             if (preference instanceof LongPressAccessPointPreference) {
442                 mSelectedAccessPoint =
443                         ((LongPressAccessPointPreference) preference).getAccessPoint();
444                 menu.setHeaderTitle(mSelectedAccessPoint.getSsid());
445                 if (mSelectedAccessPoint.isConnectable()) {
446                     menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect);
447                 }
448 
449                 WifiConfiguration config = mSelectedAccessPoint.getConfig();
450                 // Some configs are ineditable
451                 if (isEditabilityLockedDown(getActivity(), config)) {
452                     return;
453                 }
454 
455                 if (mSelectedAccessPoint.isSaved() || mSelectedAccessPoint.isEphemeral()) {
456                     // Allow forgetting a network if either the network is saved or ephemerally
457                     // connected. (In the latter case, "forget" blacklists the network so it won't
458                     // be used again, ephemerally).
459                     menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget);
460                 }
461                 if (mSelectedAccessPoint.isSaved()) {
462                     menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify);
463                     NfcAdapter nfcAdapter = NfcAdapter.getDefaultAdapter(getActivity());
464                     if (nfcAdapter != null && nfcAdapter.isEnabled() &&
465                             mSelectedAccessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
466                         // Only allow writing of NFC tags for password-protected networks.
467                         menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc);
468                     }
469                 }
470             }
471     }
472 
473     @Override
onContextItemSelected(MenuItem item)474     public boolean onContextItemSelected(MenuItem item) {
475         if (mSelectedAccessPoint == null) {
476             return super.onContextItemSelected(item);
477         }
478         switch (item.getItemId()) {
479             case MENU_ID_CONNECT: {
480                 if (mSelectedAccessPoint.isSaved()) {
481                     connect(mSelectedAccessPoint.getConfig());
482                 } else if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE) {
483                     /** Bypass dialog for unsecured networks */
484                     mSelectedAccessPoint.generateOpenNetworkConfig();
485                     connect(mSelectedAccessPoint.getConfig());
486                 } else {
487                     showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_CONNECT);
488                 }
489                 return true;
490             }
491             case MENU_ID_FORGET: {
492                 forget();
493                 return true;
494             }
495             case MENU_ID_MODIFY: {
496                 showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_MODIFY);
497                 return true;
498             }
499             case MENU_ID_WRITE_NFC:
500                 showDialog(WRITE_NFC_DIALOG_ID);
501                 return true;
502 
503         }
504         return super.onContextItemSelected(item);
505     }
506 
507     @Override
onPreferenceTreeClick(Preference preference)508     public boolean onPreferenceTreeClick(Preference preference) {
509         if (preference instanceof LongPressAccessPointPreference) {
510             mSelectedAccessPoint = ((LongPressAccessPointPreference) preference).getAccessPoint();
511             if (mSelectedAccessPoint == null) {
512                 return false;
513             }
514             /** Bypass dialog for unsecured, unsaved, and inactive networks */
515             if (mSelectedAccessPoint.getSecurity() == AccessPoint.SECURITY_NONE &&
516                     !mSelectedAccessPoint.isSaved() && !mSelectedAccessPoint.isActive()) {
517                 mSelectedAccessPoint.generateOpenNetworkConfig();
518                 connect(mSelectedAccessPoint.getConfig());
519             } else if (mSelectedAccessPoint.isSaved()) {
520                 showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_VIEW);
521             } else {
522                 showDialog(mSelectedAccessPoint, WifiConfigUiBase.MODE_CONNECT);
523             }
524         } else if (preference == mAddPreference) {
525             onAddNetworkPressed();
526         } else {
527             return super.onPreferenceTreeClick(preference);
528         }
529         return true;
530     }
531 
showDialog(AccessPoint accessPoint, int dialogMode)532     private void showDialog(AccessPoint accessPoint, int dialogMode) {
533         if (accessPoint != null) {
534             WifiConfiguration config = accessPoint.getConfig();
535             if (isEditabilityLockedDown(getActivity(), config) && accessPoint.isActive()) {
536                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(),
537                         RestrictedLockUtils.getDeviceOwner(getActivity()));
538                 return;
539             }
540         }
541 
542         if (mDialog != null) {
543             removeDialog(WIFI_DIALOG_ID);
544             mDialog = null;
545         }
546 
547         // Save the access point and edit mode
548         mDlgAccessPoint = accessPoint;
549         mDialogMode = dialogMode;
550 
551         showDialog(WIFI_DIALOG_ID);
552     }
553 
554     @Override
onCreateDialog(int dialogId)555     public Dialog onCreateDialog(int dialogId) {
556         switch (dialogId) {
557             case WIFI_DIALOG_ID:
558                 AccessPoint ap = mDlgAccessPoint; // For manual launch
559                 if (ap == null) { // For re-launch from saved state
560                     if (mAccessPointSavedState != null) {
561                         ap = new AccessPoint(getActivity(), mAccessPointSavedState);
562                         // For repeated orientation changes
563                         mDlgAccessPoint = ap;
564                         // Reset the saved access point data
565                         mAccessPointSavedState = null;
566                     }
567                 }
568                 // If it's null, fine, it's for Add Network
569                 mSelectedAccessPoint = ap;
570                 mDialog = new WifiDialog(getActivity(), this, ap, mDialogMode,
571                         /* no hide submit/connect */ false);
572                 return mDialog;
573             case WPS_PBC_DIALOG_ID:
574                 return new WpsDialog(getActivity(), WpsInfo.PBC);
575             case WPS_PIN_DIALOG_ID:
576                 return new WpsDialog(getActivity(), WpsInfo.DISPLAY);
577             case WRITE_NFC_DIALOG_ID:
578                 if (mSelectedAccessPoint != null) {
579                     mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
580                             getActivity(), mSelectedAccessPoint.getConfig().networkId,
581                             mSelectedAccessPoint.getSecurity(),
582                             mWifiManager);
583                 } else if (mWifiNfcDialogSavedState != null) {
584                     mWifiToNfcDialog = new WriteWifiConfigToNfcDialog(
585                             getActivity(), mWifiNfcDialogSavedState, mWifiManager);
586                 }
587 
588                 return mWifiToNfcDialog;
589         }
590         return super.onCreateDialog(dialogId);
591     }
592 
593     /**
594      * Shows the latest access points available with supplemental information like
595      * the strength of network and the security for it.
596      */
597     @Override
onAccessPointsChanged()598     public void onAccessPointsChanged() {
599         // Safeguard from some delayed event handling
600         if (getActivity() == null) return;
601         if (isUiRestricted()) {
602             if (!isUiRestrictedByOnlyAdmin()) {
603                 addMessagePreference(R.string.wifi_empty_list_user_restricted);
604             }
605             getPreferenceScreen().removeAll();
606             return;
607         }
608         final int wifiState = mWifiManager.getWifiState();
609 
610         switch (wifiState) {
611             case WifiManager.WIFI_STATE_ENABLED:
612                 // AccessPoints are automatically sorted with TreeSet.
613                 final Collection<AccessPoint> accessPoints =
614                         mWifiTracker.getAccessPoints();
615                 getPreferenceScreen().removeAll();
616 
617                 boolean hasAvailableAccessPoints = false;
618                 int index = 0;
619                 cacheRemoveAllPrefs(getPreferenceScreen());
620                 for (AccessPoint accessPoint : accessPoints) {
621                     // Ignore access points that are out of range.
622                     if (accessPoint.getLevel() != -1) {
623                         String key = accessPoint.getBssid();
624                         hasAvailableAccessPoints = true;
625                         LongPressAccessPointPreference pref = (LongPressAccessPointPreference)
626                                 getCachedPreference(key);
627                         if (pref != null) {
628                             pref.setOrder(index++);
629                             continue;
630                         }
631                         LongPressAccessPointPreference
632                                 preference = new LongPressAccessPointPreference(accessPoint,
633                                 getPrefContext(), mUserBadgeCache, false, this);
634                         preference.setKey(key);
635                         preference.setOrder(index++);
636 
637                         if (mOpenSsid != null && mOpenSsid.equals(accessPoint.getSsidStr())
638                                 && !accessPoint.isSaved()
639                                 && accessPoint.getSecurity() != AccessPoint.SECURITY_NONE) {
640                             onPreferenceTreeClick(preference);
641                             mOpenSsid = null;
642                         }
643                         getPreferenceScreen().addPreference(preference);
644                         accessPoint.setListener(this);
645                     }
646                 }
647                 removeCachedPrefs(getPreferenceScreen());
648                 if (!hasAvailableAccessPoints) {
649                     setProgressBarVisible(true);
650                     Preference pref = new Preference(getContext()) {
651                         @Override
652                         public void onBindViewHolder(PreferenceViewHolder holder) {
653                             super.onBindViewHolder(holder);
654                             // Show a line on each side of add network.
655                             holder.setDividerAllowedBelow(true);
656                         }
657                     };
658                     pref.setSelectable(false);
659                     pref.setSummary(R.string.wifi_empty_list_wifi_on);
660                     pref.setOrder(0);
661                     getPreferenceScreen().addPreference(pref);
662                     mAddPreference.setOrder(1);
663                     getPreferenceScreen().addPreference(mAddPreference);
664                 } else {
665                     mAddPreference.setOrder(index++);
666                     getPreferenceScreen().addPreference(mAddPreference);
667                     setProgressBarVisible(false);
668                 }
669                 if (mScanMenuItem != null) {
670                     mScanMenuItem.setEnabled(true);
671                 }
672                 break;
673 
674             case WifiManager.WIFI_STATE_ENABLING:
675                 getPreferenceScreen().removeAll();
676                 setProgressBarVisible(true);
677                 break;
678 
679             case WifiManager.WIFI_STATE_DISABLING:
680                 addMessagePreference(R.string.wifi_stopping);
681                 setProgressBarVisible(true);
682                 break;
683 
684             case WifiManager.WIFI_STATE_DISABLED:
685                 setOffMessage();
686                 setProgressBarVisible(false);
687                 if (mScanMenuItem != null) {
688                     mScanMenuItem.setEnabled(false);
689                 }
690                 break;
691         }
692     }
693 
setOffMessage()694     private void setOffMessage() {
695         if (isUiRestricted()) {
696             if (!isUiRestrictedByOnlyAdmin()) {
697                 addMessagePreference(R.string.wifi_empty_list_user_restricted);
698             }
699             getPreferenceScreen().removeAll();
700             return;
701         }
702 
703         TextView emptyTextView = getEmptyTextView();
704         if (emptyTextView == null) {
705             return;
706         }
707 
708         final CharSequence briefText = getText(R.string.wifi_empty_list_wifi_off);
709 
710         // Don't use WifiManager.isScanAlwaysAvailable() to check the Wi-Fi scanning mode. Instead,
711         // read the system settings directly. Because when the device is in Airplane mode, even if
712         // Wi-Fi scanning mode is on, WifiManager.isScanAlwaysAvailable() still returns "off".
713         final ContentResolver resolver = getActivity().getContentResolver();
714         final boolean wifiScanningMode = Settings.Global.getInt(
715                 resolver, Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1;
716 
717         if (!wifiScanningMode) {
718             // Show only the brief text if the user is not allowed to configure scanning settings,
719             // or the scanning mode has been turned off.
720             emptyTextView.setText(briefText, BufferType.SPANNABLE);
721         } else {
722             // Append the description of scanning settings with link.
723             final StringBuilder contentBuilder = new StringBuilder();
724             contentBuilder.append(briefText);
725             contentBuilder.append("\n\n");
726             contentBuilder.append(getText(R.string.wifi_scan_notify_text));
727             LinkifyUtils.linkify(emptyTextView, contentBuilder, new LinkifyUtils.OnClickListener() {
728                 @Override
729                 public void onClick() {
730                     final SettingsActivity activity =
731                             (SettingsActivity) WifiSettings.this.getActivity();
732                     activity.startPreferencePanel(ScanningSettings.class.getName(), null,
733                             R.string.location_scanning_screen_title, null, null, 0);
734                 }
735             });
736         }
737         // Embolden and enlarge the brief description anyway.
738         Spannable boldSpan = (Spannable) emptyTextView.getText();
739         boldSpan.setSpan(
740                 new TextAppearanceSpan(getActivity(), android.R.style.TextAppearance_Medium), 0,
741                 briefText.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
742         getPreferenceScreen().removeAll();
743     }
744 
addMessagePreference(int messageId)745     private void addMessagePreference(int messageId) {
746         TextView emptyTextView = getEmptyTextView();
747         if (emptyTextView != null) emptyTextView.setText(messageId);
748         getPreferenceScreen().removeAll();
749     }
750 
setProgressBarVisible(boolean visible)751     protected void setProgressBarVisible(boolean visible) {
752         if (mProgressHeader != null) {
753             mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE);
754         }
755     }
756 
757     @Override
onWifiStateChanged(int state)758     public void onWifiStateChanged(int state) {
759         switch (state) {
760             case WifiManager.WIFI_STATE_ENABLING:
761                 addMessagePreference(R.string.wifi_starting);
762                 setProgressBarVisible(true);
763                 break;
764 
765             case WifiManager.WIFI_STATE_DISABLED:
766                 setOffMessage();
767                 setProgressBarVisible(false);
768                 break;
769         }
770     }
771 
772     @Override
onConnectedChanged()773     public void onConnectedChanged() {
774         changeNextButtonState(mWifiTracker.isConnected());
775     }
776 
777     /**
778      * Renames/replaces "Next" button when appropriate. "Next" button usually exists in
779      * Wifi setup screens, not in usual wifi settings screen.
780      *
781      * @param enabled true when the device is connected to a wifi network.
782      */
changeNextButtonState(boolean enabled)783     private void changeNextButtonState(boolean enabled) {
784         if (mEnableNextOnConnection && hasNextButton()) {
785             getNextButton().setEnabled(enabled);
786         }
787     }
788 
789     @Override
onForget(WifiDialog dialog)790     public void onForget(WifiDialog dialog) {
791         forget();
792     }
793 
794     @Override
onSubmit(WifiDialog dialog)795     public void onSubmit(WifiDialog dialog) {
796         if (mDialog != null) {
797             submit(mDialog.getController());
798         }
799     }
800 
submit(WifiConfigController configController)801     /* package */ void submit(WifiConfigController configController) {
802 
803         final WifiConfiguration config = configController.getConfig();
804 
805         if (config == null) {
806             if (mSelectedAccessPoint != null
807                     && mSelectedAccessPoint.isSaved()) {
808                 connect(mSelectedAccessPoint.getConfig());
809             }
810         } else if (configController.getMode() == WifiConfigUiBase.MODE_MODIFY) {
811             mWifiManager.save(config, mSaveListener);
812         } else {
813             mWifiManager.save(config, mSaveListener);
814             if (mSelectedAccessPoint != null) { // Not an "Add network"
815                 connect(config);
816             }
817         }
818 
819         mWifiTracker.resumeScanning();
820     }
821 
forget()822     /* package */ void forget() {
823         MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_FORGET);
824         if (!mSelectedAccessPoint.isSaved()) {
825             if (mSelectedAccessPoint.getNetworkInfo() != null &&
826                     mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED) {
827                 // Network is active but has no network ID - must be ephemeral.
828                 mWifiManager.disableEphemeralNetwork(
829                         AccessPoint.convertToQuotedString(mSelectedAccessPoint.getSsidStr()));
830             } else {
831                 // Should not happen, but a monkey seems to trigger it
832                 Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig());
833                 return;
834             }
835         } else {
836             mWifiManager.forget(mSelectedAccessPoint.getConfig().networkId, mForgetListener);
837         }
838 
839         mWifiTracker.resumeScanning();
840 
841         // We need to rename/replace "Next" button in wifi setup context.
842         changeNextButtonState(false);
843     }
844 
connect(final WifiConfiguration config)845     protected void connect(final WifiConfiguration config) {
846         MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT);
847         mWifiManager.connect(config, mConnectListener);
848     }
849 
connect(final int networkId)850     protected void connect(final int networkId) {
851         MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_CONNECT);
852         mWifiManager.connect(networkId, mConnectListener);
853     }
854 
855     /**
856      * Called when "add network" button is pressed.
857      */
onAddNetworkPressed()858     /* package */ void onAddNetworkPressed() {
859         MetricsLogger.action(getActivity(), MetricsEvent.ACTION_WIFI_ADD_NETWORK);
860         // No exact access point is selected.
861         mSelectedAccessPoint = null;
862         showDialog(null, WifiConfigUiBase.MODE_CONNECT);
863     }
864 
865     @Override
getHelpResource()866     protected int getHelpResource() {
867         return R.string.help_url_wifi;
868     }
869 
870     @Override
onAccessPointChanged(AccessPoint accessPoint)871     public void onAccessPointChanged(AccessPoint accessPoint) {
872         ((LongPressAccessPointPreference) accessPoint.getTag()).refresh();
873     }
874 
875     @Override
onLevelChanged(AccessPoint accessPoint)876     public void onLevelChanged(AccessPoint accessPoint) {
877         ((LongPressAccessPointPreference) accessPoint.getTag()).onLevelChanged();
878     }
879 
880     public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
881         new BaseSearchIndexProvider() {
882             @Override
883             public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) {
884                 final List<SearchIndexableRaw> result = new ArrayList<>();
885                 final Resources res = context.getResources();
886 
887                 // Add fragment title
888                 SearchIndexableRaw data = new SearchIndexableRaw(context);
889                 data.title = res.getString(R.string.wifi_settings);
890                 data.screenTitle = res.getString(R.string.wifi_settings);
891                 data.keywords = res.getString(R.string.keywords_wifi);
892                 result.add(data);
893 
894                 // Add saved Wi-Fi access points
895                 final Collection<AccessPoint> accessPoints =
896                         WifiTracker.getCurrentAccessPoints(context, true, false, false);
897                 for (AccessPoint accessPoint : accessPoints) {
898                     data = new SearchIndexableRaw(context);
899                     data.title = accessPoint.getSsidStr();
900                     data.screenTitle = res.getString(R.string.wifi_settings);
901                     data.enabled = enabled;
902                     result.add(data);
903                 }
904 
905                 return result;
906             }
907         };
908 
909     /**
910      * Returns true if the config is not editable through Settings.
911      * @param context Context of caller
912      * @param config The WiFi config.
913      * @return true if the config is not editable through Settings.
914      */
isEditabilityLockedDown(Context context, WifiConfiguration config)915     static boolean isEditabilityLockedDown(Context context, WifiConfiguration config) {
916         return !canModifyNetwork(context, config);
917     }
918 
919     /**
920      * This method is a stripped version of WifiConfigStore.canModifyNetwork.
921      * TODO: refactor to have only one method.
922      * @param context Context of caller
923      * @param config The WiFi config.
924      * @return true if Settings can modify the config.
925      */
canModifyNetwork(Context context, WifiConfiguration config)926     static boolean canModifyNetwork(Context context, WifiConfiguration config) {
927         if (config == null) {
928             return true;
929         }
930 
931         final DevicePolicyManager dpm = (DevicePolicyManager) context.getSystemService(
932                 Context.DEVICE_POLICY_SERVICE);
933 
934         // Check if device has DPM capability. If it has and dpm is still null, then we
935         // treat this case with suspicion and bail out.
936         final PackageManager pm = context.getPackageManager();
937         if (pm.hasSystemFeature(PackageManager.FEATURE_DEVICE_ADMIN) && dpm == null) {
938             return false;
939         }
940 
941         boolean isConfigEligibleForLockdown = false;
942         if (dpm != null) {
943             final ComponentName deviceOwner = dpm.getDeviceOwnerComponentOnAnyUser();
944             if (deviceOwner != null) {
945                 final int deviceOwnerUserId = dpm.getDeviceOwnerUserId();
946                 try {
947                     final int deviceOwnerUid = pm.getPackageUidAsUser(deviceOwner.getPackageName(),
948                             deviceOwnerUserId);
949                     isConfigEligibleForLockdown = deviceOwnerUid == config.creatorUid;
950                 } catch (NameNotFoundException e) {
951                     // don't care
952                 }
953             }
954         }
955         if (!isConfigEligibleForLockdown) {
956             return true;
957         }
958 
959         final ContentResolver resolver = context.getContentResolver();
960         final boolean isLockdownFeatureEnabled = Settings.Global.getInt(resolver,
961                 Settings.Global.WIFI_DEVICE_OWNER_CONFIGS_LOCKDOWN, 0) != 0;
962         return !isLockdownFeatureEnabled;
963     }
964 
965     private static class SummaryProvider extends BroadcastReceiver
966             implements SummaryLoader.SummaryProvider {
967 
968         private final Context mContext;
969         private final WifiManager mWifiManager;
970         private final WifiStatusTracker mWifiTracker;
971         private final SummaryLoader mSummaryLoader;
972 
SummaryProvider(Context context, SummaryLoader summaryLoader)973         public SummaryProvider(Context context, SummaryLoader summaryLoader) {
974             mContext = context;
975             mSummaryLoader = summaryLoader;
976             mWifiManager = context.getSystemService(WifiManager.class);
977             mWifiTracker = new WifiStatusTracker(mWifiManager);
978         }
979 
getSummary()980         private CharSequence getSummary() {
981             if (!mWifiTracker.enabled) {
982                 return mContext.getString(R.string.wifi_disabled_generic);
983             }
984             if (!mWifiTracker.connected) {
985                 return mContext.getString(R.string.disconnected);
986             }
987             return mWifiTracker.ssid;
988         }
989 
990         @Override
setListening(boolean listening)991         public void setListening(boolean listening) {
992             if (listening) {
993                 IntentFilter filter = new IntentFilter();
994                 filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
995                 filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
996                 filter.addAction(WifiManager.RSSI_CHANGED_ACTION);
997                 mSummaryLoader.registerReceiver(this, filter);
998             }
999         }
1000 
1001         @Override
onReceive(Context context, Intent intent)1002         public void onReceive(Context context, Intent intent) {
1003             mWifiTracker.handleBroadcast(intent);
1004             mSummaryLoader.setSummary(this, getSummary());
1005         }
1006     }
1007 
1008     public static final SummaryLoader.SummaryProviderFactory SUMMARY_PROVIDER_FACTORY
1009             = new SummaryLoader.SummaryProviderFactory() {
1010         @Override
1011         public SummaryLoader.SummaryProvider createSummaryProvider(Activity activity,
1012                                                                    SummaryLoader summaryLoader) {
1013             return new SummaryProvider(activity, summaryLoader);
1014         }
1015     };
1016 }
1017