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