1 /* 2 * Copyright (C) 2019 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.wifi.WifiConfiguration.NetworkSelectionStatus.NETWORK_SELECTION_ENABLED; 20 import static android.os.UserManager.DISALLOW_CONFIG_WIFI; 21 22 import android.app.Activity; 23 import android.app.Dialog; 24 import android.app.settings.SettingsEnums; 25 import android.content.ActivityNotFoundException; 26 import android.content.ContentResolver; 27 import android.content.Context; 28 import android.content.DialogInterface; 29 import android.content.Intent; 30 import android.net.ConnectivityManager; 31 import android.net.NetworkScoreManager; 32 import android.net.NetworkTemplate; 33 import android.net.wifi.WifiConfiguration; 34 import android.net.wifi.WifiManager; 35 import android.os.Bundle; 36 import android.os.Handler; 37 import android.os.HandlerThread; 38 import android.os.Looper; 39 import android.os.PowerManager; 40 import android.os.Process; 41 import android.os.SimpleClock; 42 import android.os.SystemClock; 43 import android.provider.Settings; 44 import android.text.TextUtils; 45 import android.util.FeatureFlagUtils; 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.MenuItem; 51 import android.view.View; 52 import android.widget.Toast; 53 54 import androidx.annotation.VisibleForTesting; 55 import androidx.preference.Preference; 56 import androidx.preference.PreferenceCategory; 57 import androidx.preference.PreferenceScreen; 58 import androidx.recyclerview.widget.RecyclerView; 59 60 import com.android.settings.LinkifyUtils; 61 import com.android.settings.R; 62 import com.android.settings.RestrictedSettingsFragment; 63 import com.android.settings.SettingsActivity; 64 import com.android.settings.core.FeatureFlags; 65 import com.android.settings.core.SubSettingLauncher; 66 import com.android.settings.datausage.DataUsagePreference; 67 import com.android.settings.datausage.DataUsageUtils; 68 import com.android.settings.location.ScanningSettings; 69 import com.android.settings.search.BaseSearchIndexProvider; 70 import com.android.settings.widget.SwitchBarController; 71 import com.android.settings.wifi.details2.WifiNetworkDetailsFragment2; 72 import com.android.settings.wifi.dpp.WifiDppUtils; 73 import com.android.settingslib.HelpUtils; 74 import com.android.settingslib.RestrictedLockUtils; 75 import com.android.settingslib.RestrictedLockUtilsInternal; 76 import com.android.settingslib.search.Indexable; 77 import com.android.settingslib.search.SearchIndexable; 78 import com.android.settingslib.wifi.LongPressWifiEntryPreference; 79 import com.android.settingslib.wifi.WifiSavedConfigUtils; 80 import com.android.wifitrackerlib.WifiEntry; 81 import com.android.wifitrackerlib.WifiEntry.ConnectCallback; 82 import com.android.wifitrackerlib.WifiPickerTracker; 83 84 import java.time.Clock; 85 import java.time.ZoneOffset; 86 import java.util.List; 87 import java.util.Optional; 88 89 /** 90 * UI for Wi-Fi settings screen 91 */ 92 @SearchIndexable 93 public class WifiSettings2 extends RestrictedSettingsFragment 94 implements Indexable, WifiPickerTracker.WifiPickerTrackerCallback, 95 WifiDialog2.WifiDialog2Listener, DialogInterface.OnDismissListener { 96 97 private static final String TAG = "WifiSettings2"; 98 99 // IDs of context menu 100 static final int MENU_ID_CONNECT = Menu.FIRST + 1; 101 @VisibleForTesting 102 static final int MENU_ID_DISCONNECT = Menu.FIRST + 2; 103 @VisibleForTesting 104 static final int MENU_ID_FORGET = Menu.FIRST + 3; 105 static final int MENU_ID_MODIFY = Menu.FIRST + 4; 106 107 // Max age of tracked WifiEntries 108 private static final long MAX_SCAN_AGE_MILLIS = 15_000; 109 // Interval between initiating WifiPickerTracker scans 110 private static final long SCAN_INTERVAL_MILLIS = 10_000; 111 112 @VisibleForTesting 113 static final int ADD_NETWORK_REQUEST = 2; 114 static final int CONFIG_NETWORK_REQUEST = 3; 115 static final int MANAGE_SUBSCRIPTION = 4; 116 117 private static final String PREF_KEY_EMPTY_WIFI_LIST = "wifi_empty_list"; 118 // TODO(b/70983952): Rename these to use WifiEntry instead of AccessPoint. 119 private static final String PREF_KEY_CONNECTED_ACCESS_POINTS = "connected_access_point"; 120 private static final String PREF_KEY_ACCESS_POINTS = "access_points"; 121 private static final String PREF_KEY_CONFIGURE_WIFI_SETTINGS = "configure_wifi_settings"; 122 private static final String PREF_KEY_SAVED_NETWORKS = "saved_networks"; 123 private static final String PREF_KEY_STATUS_MESSAGE = "wifi_status_message"; 124 @VisibleForTesting 125 static final String PREF_KEY_DATA_USAGE = "wifi_data_usage"; 126 127 private static final int REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER = 0; 128 129 public static final int WIFI_DIALOG_ID = 1; 130 131 // Instance state keys 132 private static final String SAVE_DIALOG_MODE = "dialog_mode"; 133 private static final String SAVE_DIALOG_WIFIENTRY_KEY = "wifi_ap_key"; 134 135 // Cache at onCreateContextMenu and use at onContextItemSelected. Don't use it in other methods. 136 private WifiEntry mSelectedWifiEntry; 137 138 // Save the dialog details 139 private int mDialogMode; 140 private String mDialogWifiEntryKey; 141 private WifiEntry mDialogWifiEntry; 142 143 // This boolean extra specifies whether to enable the Next button when connected. Used by 144 // account creation outside of setup wizard. 145 private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect"; 146 147 // Enable the Next button when a Wi-Fi network is connected. 148 private boolean mEnableNextOnConnection; 149 150 // This string extra specifies a network to open the connect dialog on, so the user can enter 151 // network credentials. This is used by quick settings for secured networks, among other 152 // things. 153 private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid"; 154 private String mOpenSsid; 155 isVerboseLoggingEnabled()156 private static boolean isVerboseLoggingEnabled() { 157 return WifiPickerTracker.isVerboseLoggingEnabled(); 158 } 159 160 private final Runnable mUpdateWifiEntryPreferencesRunnable = () -> { 161 updateWifiEntryPreferences(); 162 }; 163 private final Runnable mHideProgressBarRunnable = () -> { 164 setProgressBarVisible(false); 165 }; 166 167 protected WifiManager mWifiManager; 168 private WifiManager.ActionListener mConnectListener; 169 private WifiManager.ActionListener mSaveListener; 170 private WifiManager.ActionListener mForgetListener; 171 172 /** 173 * The state of {@link #isUiRestricted()} at {@link #onCreate(Bundle)}}. This is neccesary to 174 * ensure that behavior is consistent if {@link #isUiRestricted()} changes. It could be changed 175 * by the Test DPC tool in AFW mode. 176 */ 177 private boolean mIsRestricted; 178 179 private WifiEnabler mWifiEnabler; 180 181 // Worker thread used for WifiPickerTracker work 182 private HandlerThread mWorkerThread; 183 184 @VisibleForTesting 185 WifiPickerTracker mWifiPickerTracker; 186 187 private WifiDialog2 mDialog; 188 189 private View mProgressHeader; 190 191 private PreferenceCategory mConnectedWifiEntryPreferenceCategory; 192 private PreferenceCategory mWifiEntryPreferenceCategory; 193 @VisibleForTesting 194 AddWifiNetworkPreference mAddWifiNetworkPreference; 195 @VisibleForTesting 196 Preference mConfigureWifiSettingsPreference; 197 @VisibleForTesting 198 Preference mSavedNetworksPreference; 199 @VisibleForTesting 200 DataUsagePreference mDataUsagePreference; 201 private LinkablePreference mStatusMessagePreference; 202 203 /** 204 * Tracks whether the user initiated a connection via clicking in order to autoscroll to the 205 * network once connected. 206 */ 207 private boolean mClickedConnect; 208 WifiSettings2()209 public WifiSettings2() { 210 super(DISALLOW_CONFIG_WIFI); 211 } 212 213 @Override onViewCreated(View view, Bundle savedInstanceState)214 public void onViewCreated(View view, Bundle savedInstanceState) { 215 super.onViewCreated(view, savedInstanceState); 216 final Activity activity = getActivity(); 217 if (activity != null) { 218 mProgressHeader = setPinnedHeaderView(R.layout.progress_header) 219 .findViewById(R.id.progress_bar_animation); 220 setProgressBarVisible(false); 221 } 222 ((SettingsActivity) activity).getSwitchBar().setSwitchBarText( 223 R.string.wifi_settings_master_switch_title, 224 R.string.wifi_settings_master_switch_title); 225 } 226 227 @Override onCreate(Bundle icicle)228 public void onCreate(Bundle icicle) { 229 super.onCreate(icicle); 230 231 // TODO(b/37429702): Add animations and preference comparator back after initial screen is 232 // loaded (ODR). 233 setAnimationAllowed(false); 234 235 addPreferences(); 236 237 mIsRestricted = isUiRestricted(); 238 } 239 addPreferences()240 private void addPreferences() { 241 addPreferencesFromResource(R.xml.wifi_settings2); 242 243 mConnectedWifiEntryPreferenceCategory = findPreference(PREF_KEY_CONNECTED_ACCESS_POINTS); 244 mWifiEntryPreferenceCategory = findPreference(PREF_KEY_ACCESS_POINTS); 245 mConfigureWifiSettingsPreference = findPreference(PREF_KEY_CONFIGURE_WIFI_SETTINGS); 246 mSavedNetworksPreference = findPreference(PREF_KEY_SAVED_NETWORKS); 247 mAddWifiNetworkPreference = new AddWifiNetworkPreference(getPrefContext()); 248 mStatusMessagePreference = findPreference(PREF_KEY_STATUS_MESSAGE); 249 mDataUsagePreference = findPreference(PREF_KEY_DATA_USAGE); 250 mDataUsagePreference.setVisible(DataUsageUtils.hasWifiRadio(getContext())); 251 mDataUsagePreference.setTemplate(NetworkTemplate.buildTemplateWifiWildcard(), 252 0 /*subId*/, 253 null /*service*/); 254 } 255 256 @Override onActivityCreated(Bundle savedInstanceState)257 public void onActivityCreated(Bundle savedInstanceState) { 258 super.onActivityCreated(savedInstanceState); 259 260 final Context context = getContext(); 261 mWorkerThread = new HandlerThread(TAG + 262 "{" + Integer.toHexString(System.identityHashCode(this)) + "}", 263 Process.THREAD_PRIORITY_BACKGROUND); 264 mWorkerThread.start(); 265 final Clock elapsedRealtimeClock = new SimpleClock(ZoneOffset.UTC) { 266 @Override 267 public long millis() { 268 return SystemClock.elapsedRealtime(); 269 } 270 }; 271 mWifiPickerTracker = new WifiPickerTracker(getSettingsLifecycle(), context, 272 context.getSystemService(WifiManager.class), 273 context.getSystemService(ConnectivityManager.class), 274 context.getSystemService(NetworkScoreManager.class), 275 new Handler(Looper.getMainLooper()), 276 mWorkerThread.getThreadHandler(), 277 elapsedRealtimeClock, 278 MAX_SCAN_AGE_MILLIS, 279 SCAN_INTERVAL_MILLIS, 280 this); 281 282 final Activity activity = getActivity(); 283 284 if (activity != null) { 285 mWifiManager = getActivity().getSystemService(WifiManager.class); 286 } 287 288 mConnectListener = new WifiConnectListener(getActivity()); 289 290 mSaveListener = new WifiManager.ActionListener() { 291 @Override 292 public void onSuccess() { 293 } 294 295 @Override 296 public void onFailure(int reason) { 297 Activity activity = getActivity(); 298 if (activity != null) { 299 Toast.makeText(activity, 300 R.string.wifi_failed_save_message, 301 Toast.LENGTH_SHORT).show(); 302 } 303 } 304 }; 305 306 mForgetListener = new WifiManager.ActionListener() { 307 @Override 308 public void onSuccess() { 309 } 310 311 @Override 312 public void onFailure(int reason) { 313 Activity activity = getActivity(); 314 if (activity != null) { 315 Toast.makeText(activity, 316 R.string.wifi_failed_forget_message, 317 Toast.LENGTH_SHORT).show(); 318 } 319 } 320 }; 321 registerForContextMenu(getListView()); 322 setHasOptionsMenu(true); 323 324 if (savedInstanceState != null) { 325 mDialogMode = savedInstanceState.getInt(SAVE_DIALOG_MODE); 326 mDialogWifiEntryKey = savedInstanceState.getString(SAVE_DIALOG_WIFIENTRY_KEY); 327 } 328 329 // If we're supposed to enable/disable the Next button based on our current connection 330 // state, start it off in the right state. 331 final Intent intent = getActivity().getIntent(); 332 mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false); 333 334 if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) { 335 mOpenSsid = intent.getStringExtra(EXTRA_START_CONNECT_SSID); 336 } 337 } 338 339 @Override onDestroyView()340 public void onDestroyView() { 341 if (mWifiEnabler != null) { 342 mWifiEnabler.teardownSwitchController(); 343 } 344 mWorkerThread.quit(); 345 346 super.onDestroyView(); 347 } 348 349 @Override onStart()350 public void onStart() { 351 super.onStart(); 352 353 mWifiEnabler = createWifiEnabler(); 354 355 if (mIsRestricted) { 356 restrictUi(); 357 } 358 } 359 restrictUi()360 private void restrictUi() { 361 if (!isUiRestrictedByOnlyAdmin()) { 362 getEmptyTextView().setText(R.string.wifi_empty_list_user_restricted); 363 } 364 getPreferenceScreen().removeAll(); 365 } 366 367 /** 368 * @return new WifiEnabler 369 */ createWifiEnabler()370 private WifiEnabler createWifiEnabler() { 371 final SettingsActivity activity = (SettingsActivity) getActivity(); 372 return new WifiEnabler(activity, new SwitchBarController(activity.getSwitchBar()), 373 mMetricsFeatureProvider); 374 } 375 376 @Override onResume()377 public void onResume() { 378 final Activity activity = getActivity(); 379 super.onResume(); 380 381 // Because RestrictedSettingsFragment's onResume potentially requests authorization, 382 // which changes the restriction state, recalculate it. 383 final boolean alreadyImmutablyRestricted = mIsRestricted; 384 mIsRestricted = isUiRestricted(); 385 if (!alreadyImmutablyRestricted && mIsRestricted) { 386 restrictUi(); 387 } 388 389 if (mWifiEnabler != null) { 390 mWifiEnabler.resume(activity); 391 } 392 393 changeNextButtonState(mWifiPickerTracker.getConnectedWifiEntry() != null); 394 } 395 396 @Override onPause()397 public void onPause() { 398 super.onPause(); 399 if (mWifiEnabler != null) { 400 mWifiEnabler.pause(); 401 } 402 } 403 404 @Override onStop()405 public void onStop() { 406 getView().removeCallbacks(mUpdateWifiEntryPreferencesRunnable); 407 getView().removeCallbacks(mHideProgressBarRunnable); 408 super.onStop(); 409 } 410 411 @Override onActivityResult(int requestCode, int resultCode, Intent data)412 public void onActivityResult(int requestCode, int resultCode, Intent data) { 413 super.onActivityResult(requestCode, resultCode, data); 414 415 if (requestCode == ADD_NETWORK_REQUEST) { 416 handleAddNetworkRequest(resultCode, data); 417 return; 418 } else if (requestCode == REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER) { 419 if (resultCode == Activity.RESULT_OK) { 420 if (mDialog != null) { 421 mDialog.dismiss(); 422 } 423 } 424 return; 425 } else if (requestCode == CONFIG_NETWORK_REQUEST) { 426 if (resultCode == Activity.RESULT_OK) { 427 final WifiConfiguration wifiConfiguration = data.getParcelableExtra( 428 ConfigureWifiEntryFragment.NETWORK_CONFIG_KEY); 429 if (wifiConfiguration != null) { 430 mWifiManager.connect(wifiConfiguration, 431 new WifiConnectActionListener()); 432 } 433 } 434 return; 435 } else if (requestCode == MANAGE_SUBSCRIPTION) { 436 //Do nothing 437 return; 438 } 439 440 final boolean formerlyRestricted = mIsRestricted; 441 mIsRestricted = isUiRestricted(); 442 if (formerlyRestricted && !mIsRestricted 443 && getPreferenceScreen().getPreferenceCount() == 0) { 444 // De-restrict the ui 445 addPreferences(); 446 } 447 } 448 449 @Override onCreateAdapter(PreferenceScreen preferenceScreen)450 protected RecyclerView.Adapter onCreateAdapter(PreferenceScreen preferenceScreen) { 451 final RecyclerView.Adapter adapter = super.onCreateAdapter(preferenceScreen); 452 adapter.setHasStableIds(true); 453 return adapter; 454 } 455 456 @Override getMetricsCategory()457 public int getMetricsCategory() { 458 return SettingsEnums.WIFI; 459 } 460 461 @Override onSaveInstanceState(Bundle outState)462 public void onSaveInstanceState(Bundle outState) { 463 super.onSaveInstanceState(outState); 464 // If dialog has been shown, save its state. 465 if (mDialog != null) { 466 outState.putInt(SAVE_DIALOG_MODE, mDialogMode); 467 outState.putString(SAVE_DIALOG_WIFIENTRY_KEY, mDialogWifiEntryKey); 468 } 469 } 470 471 @Override onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info)472 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) { 473 Preference preference = (Preference) view.getTag(); 474 if (!(preference instanceof LongPressWifiEntryPreference)) { 475 // Do nothing. 476 return; 477 } 478 479 // Cache the WifiEntry for onContextItemSelected. Don't use it in other methods. 480 mSelectedWifiEntry = ((LongPressWifiEntryPreference) preference).getWifiEntry(); 481 482 menu.setHeaderTitle(mSelectedWifiEntry.getTitle()); 483 if (mSelectedWifiEntry.canConnect()) { 484 menu.add(Menu.NONE, MENU_ID_CONNECT, 0 /* order */, R.string.wifi_connect); 485 } 486 487 if (mSelectedWifiEntry.canDisconnect()) { 488 menu.add(Menu.NONE, MENU_ID_DISCONNECT, 0 /* order */, 489 R.string.wifi_disconnect_button_text); 490 } 491 492 // "forget" for normal saved network. And "disconnect" for ephemeral network because it 493 // could only be disconnected and be put in blacklists so it won't be used again. 494 if (canForgetNetwork()) { 495 menu.add(Menu.NONE, MENU_ID_FORGET, 0 /* order */, R.string.forget); 496 } 497 498 WifiConfiguration config = mSelectedWifiEntry.getWifiConfiguration(); 499 // Some configs are ineditable 500 if (WifiUtils.isNetworkLockedDown(getActivity(), config)) { 501 return; 502 } 503 504 if (mSelectedWifiEntry.isSaved() && mSelectedWifiEntry.getConnectedState() 505 != WifiEntry.CONNECTED_STATE_CONNECTED) { 506 menu.add(Menu.NONE, MENU_ID_MODIFY, 0 /* order */, R.string.wifi_modify); 507 } 508 } 509 canForgetNetwork()510 private boolean canForgetNetwork() { 511 return mSelectedWifiEntry.canForget() && !WifiUtils.isNetworkLockedDown(getActivity(), 512 mSelectedWifiEntry.getWifiConfiguration()); 513 } 514 515 @Override onContextItemSelected(MenuItem item)516 public boolean onContextItemSelected(MenuItem item) { 517 switch (item.getItemId()) { 518 case MENU_ID_CONNECT: 519 connect(mSelectedWifiEntry, true /* editIfNoConfig */, false /* fullScreenEdit */); 520 return true; 521 case MENU_ID_DISCONNECT: 522 mSelectedWifiEntry.disconnect(null /* callback */); 523 return true; 524 case MENU_ID_FORGET: 525 forget(mSelectedWifiEntry); 526 return true; 527 case MENU_ID_MODIFY: 528 showDialog(mSelectedWifiEntry, WifiConfigUiBase2.MODE_MODIFY); 529 return true; 530 default: 531 return super.onContextItemSelected(item); 532 } 533 } 534 535 @Override onPreferenceTreeClick(Preference preference)536 public boolean onPreferenceTreeClick(Preference preference) { 537 // If the preference has a fragment set, open that 538 if (preference.getFragment() != null) { 539 preference.setOnPreferenceClickListener(null); 540 return super.onPreferenceTreeClick(preference); 541 } 542 543 if (preference instanceof LongPressWifiEntryPreference) { 544 final WifiEntry selectedEntry = 545 ((LongPressWifiEntryPreference) preference).getWifiEntry(); 546 547 if (selectedEntry.shouldEditBeforeConnect()) { 548 launchConfigNewNetworkFragment(selectedEntry); 549 return true; 550 } 551 552 connect(selectedEntry, true /* editIfNoConfig */, true /* fullScreenEdit */); 553 } else if (preference == mAddWifiNetworkPreference) { 554 onAddNetworkPressed(); 555 } else { 556 return super.onPreferenceTreeClick(preference); 557 } 558 return true; 559 } 560 showDialog(WifiEntry wifiEntry, int dialogMode)561 private void showDialog(WifiEntry wifiEntry, int dialogMode) { 562 if (WifiUtils.isNetworkLockedDown(getActivity(), wifiEntry.getWifiConfiguration()) 563 && wifiEntry.getConnectedState() == WifiEntry.CONNECTED_STATE_CONNECTED) { 564 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(getActivity(), 565 RestrictedLockUtilsInternal.getDeviceOwner(getActivity())); 566 return; 567 } 568 569 if (mDialog != null) { 570 removeDialog(WIFI_DIALOG_ID); 571 mDialog = null; 572 } 573 574 // Save the access point and edit mode 575 mDialogWifiEntry = wifiEntry; 576 mDialogWifiEntryKey = wifiEntry.getKey(); 577 mDialogMode = dialogMode; 578 579 showDialog(WIFI_DIALOG_ID); 580 } 581 582 @Override onCreateDialog(int dialogId)583 public Dialog onCreateDialog(int dialogId) { 584 switch (dialogId) { 585 case WIFI_DIALOG_ID: 586 // modify network 587 mDialog = WifiDialog2 588 .createModal(getActivity(), this, mDialogWifiEntry, mDialogMode); 589 return mDialog; 590 default: 591 return super.onCreateDialog(dialogId); 592 } 593 } 594 595 @Override onDialogShowing()596 public void onDialogShowing() { 597 super.onDialogShowing(); 598 setOnDismissListener(this); 599 } 600 601 @Override onDismiss(DialogInterface dialog)602 public void onDismiss(DialogInterface dialog) { 603 // We don't keep any dialog object when dialog was dismissed. 604 mDialog = null; 605 mDialogWifiEntry = null; 606 mDialogWifiEntryKey = null; 607 } 608 609 @Override getDialogMetricsCategory(int dialogId)610 public int getDialogMetricsCategory(int dialogId) { 611 switch (dialogId) { 612 case WIFI_DIALOG_ID: 613 return SettingsEnums.DIALOG_WIFI_AP_EDIT; 614 default: 615 return 0; 616 } 617 } 618 619 /** Called when the state of Wifi has changed. */ 620 @Override onWifiStateChanged()621 public void onWifiStateChanged() { 622 if (mIsRestricted) { 623 return; 624 } 625 final int wifiState = mWifiPickerTracker.getWifiState(); 626 627 if (isVerboseLoggingEnabled()) { 628 Log.i(TAG, "onWifiStateChanged called with wifi state: " + wifiState); 629 } 630 631 switch (wifiState) { 632 case WifiManager.WIFI_STATE_ENABLED: 633 updateWifiEntryPreferences(); 634 break; 635 636 case WifiManager.WIFI_STATE_ENABLING: 637 removeConnectedWifiEntryPreference(); 638 removeWifiEntryPreference(); 639 addMessagePreference(R.string.wifi_starting); 640 setProgressBarVisible(true); 641 break; 642 643 case WifiManager.WIFI_STATE_DISABLING: 644 removeConnectedWifiEntryPreference(); 645 removeWifiEntryPreference(); 646 addMessagePreference(R.string.wifi_stopping); 647 break; 648 649 case WifiManager.WIFI_STATE_DISABLED: 650 setOffMessage(); 651 setAdditionalSettingsSummaries(); 652 setProgressBarVisible(false); 653 mClickedConnect = false; 654 break; 655 } 656 } 657 658 @Override onWifiEntriesChanged()659 public void onWifiEntriesChanged() { 660 updateWifiEntryPreferencesDelayed(); 661 changeNextButtonState(mWifiPickerTracker.getConnectedWifiEntry() != null); 662 663 // Edit the Wi-Fi network of specified SSID. 664 if (mOpenSsid != null) { 665 Optional<WifiEntry> matchedWifiEntry = mWifiPickerTracker.getWifiEntries().stream() 666 .filter(wifiEntry -> TextUtils.equals(mOpenSsid, wifiEntry.getSsid())) 667 .filter(wifiEntry -> wifiEntry.getSecurity() != WifiEntry.SECURITY_NONE 668 && wifiEntry.getSecurity() != WifiEntry.SECURITY_OWE) 669 .filter(wifiEntry -> !wifiEntry.isSaved() 670 || isDisabledByWrongPassword(wifiEntry)) 671 .findFirst(); 672 if (matchedWifiEntry.isPresent()) { 673 mOpenSsid = null; 674 launchConfigNewNetworkFragment(matchedWifiEntry.get()); 675 } 676 } 677 } 678 679 @Override onNumSavedNetworksChanged()680 public void onNumSavedNetworksChanged() { 681 if (isFinishingOrDestroyed()) { 682 return; 683 } 684 setAdditionalSettingsSummaries(); 685 } 686 687 @Override onNumSavedSubscriptionsChanged()688 public void onNumSavedSubscriptionsChanged() { 689 if (isFinishingOrDestroyed()) { 690 return; 691 } 692 setAdditionalSettingsSummaries(); 693 } 694 695 /** 696 * Updates WifiEntries from {@link WifiPickerTracker#getWifiEntries()}. Adds a delay to have 697 * progress bar displayed before starting to modify entries. 698 */ updateWifiEntryPreferencesDelayed()699 private void updateWifiEntryPreferencesDelayed() { 700 // Safeguard from some delayed event handling 701 if (getActivity() != null && !mIsRestricted && 702 mWifiPickerTracker.getWifiState() == WifiManager.WIFI_STATE_ENABLED) { 703 final View view = getView(); 704 final Handler handler = view.getHandler(); 705 if (handler != null && handler.hasCallbacks(mUpdateWifiEntryPreferencesRunnable)) { 706 return; 707 } 708 setProgressBarVisible(true); 709 view.postDelayed(mUpdateWifiEntryPreferencesRunnable, 300); 710 } 711 } 712 updateWifiEntryPreferences()713 private void updateWifiEntryPreferences() { 714 // in case state has changed 715 if (mWifiPickerTracker.getWifiState() != WifiManager.WIFI_STATE_ENABLED) { 716 return; 717 } 718 719 boolean hasAvailableWifiEntries = false; 720 mStatusMessagePreference.setVisible(false); 721 mWifiEntryPreferenceCategory.setVisible(true); 722 723 final WifiEntry connectedEntry = mWifiPickerTracker.getConnectedWifiEntry(); 724 mConnectedWifiEntryPreferenceCategory.setVisible(connectedEntry != null); 725 if (connectedEntry != null) { 726 final LongPressWifiEntryPreference connectedPref = 727 mConnectedWifiEntryPreferenceCategory.findPreference(connectedEntry.getKey()); 728 if (connectedPref == null || connectedPref.getWifiEntry() != connectedEntry) { 729 mConnectedWifiEntryPreferenceCategory.removeAll(); 730 final ConnectedWifiEntryPreference pref = 731 new ConnectedWifiEntryPreference(getPrefContext(), connectedEntry, this); 732 pref.setKey(connectedEntry.getKey()); 733 pref.refresh(); 734 mConnectedWifiEntryPreferenceCategory.addPreference(pref); 735 pref.setOnPreferenceClickListener(preference -> { 736 if (connectedEntry.canSignIn()) { 737 connectedEntry.signIn(null /* callback */); 738 } else { 739 launchNetworkDetailsFragment(pref); 740 } 741 return true; 742 }); 743 pref.setOnGearClickListener(preference -> { 744 launchNetworkDetailsFragment(pref); 745 }); 746 747 if (mClickedConnect) { 748 mClickedConnect = false; 749 scrollToPreference(mConnectedWifiEntryPreferenceCategory); 750 } 751 } 752 } else { 753 mConnectedWifiEntryPreferenceCategory.removeAll(); 754 } 755 756 int index = 0; 757 cacheRemoveAllPrefs(mWifiEntryPreferenceCategory); 758 List<WifiEntry> wifiEntries = mWifiPickerTracker.getWifiEntries(); 759 for (WifiEntry wifiEntry : wifiEntries) { 760 hasAvailableWifiEntries = true; 761 762 String key = wifiEntry.getKey(); 763 LongPressWifiEntryPreference pref = 764 (LongPressWifiEntryPreference) getCachedPreference(key); 765 if (pref != null) { 766 if (pref.getWifiEntry() == wifiEntry) { 767 pref.setOrder(index++); 768 continue; 769 } else { 770 // Create a new preference if the underlying WifiEntry object has changed 771 removePreference(key); 772 } 773 } 774 775 pref = createLongPressWifiEntryPreference(wifiEntry); 776 pref.setKey(wifiEntry.getKey()); 777 pref.setOrder(index++); 778 pref.refresh(); 779 780 if (wifiEntry.getHelpUriString() != null) { 781 pref.setOnButtonClickListener(preference -> { 782 openSubscriptionHelpPage(wifiEntry); 783 }); 784 } 785 mWifiEntryPreferenceCategory.addPreference(pref); 786 } 787 removeCachedPrefs(mWifiEntryPreferenceCategory); 788 789 if (!hasAvailableWifiEntries) { 790 setProgressBarVisible(true); 791 Preference pref = new Preference(getPrefContext()); 792 pref.setSelectable(false); 793 pref.setSummary(R.string.wifi_empty_list_wifi_on); 794 pref.setOrder(index++); 795 pref.setKey(PREF_KEY_EMPTY_WIFI_LIST); 796 mWifiEntryPreferenceCategory.addPreference(pref); 797 } else { 798 // Continuing showing progress bar for an additional delay to overlap with animation 799 getView().postDelayed(mHideProgressBarRunnable, 1700 /* delay millis */); 800 } 801 802 mAddWifiNetworkPreference.setOrder(index++); 803 mWifiEntryPreferenceCategory.addPreference(mAddWifiNetworkPreference); 804 setAdditionalSettingsSummaries(); 805 } 806 launchNetworkDetailsFragment(LongPressWifiEntryPreference pref)807 private void launchNetworkDetailsFragment(LongPressWifiEntryPreference pref) { 808 final WifiEntry wifiEntry = pref.getWifiEntry(); 809 final Context context = getContext(); 810 final CharSequence title = 811 FeatureFlagUtils.isEnabled(context, FeatureFlags.WIFI_DETAILS_DATAUSAGE_HEADER) 812 ? wifiEntry.getTitle() 813 : context.getText(R.string.pref_title_network_details); 814 815 final Bundle bundle = new Bundle(); 816 bundle.putString(WifiNetworkDetailsFragment2.KEY_CHOSEN_WIFIENTRY_KEY, wifiEntry.getKey()); 817 818 new SubSettingLauncher(context) 819 .setTitleText(title) 820 .setDestination(WifiNetworkDetailsFragment2.class.getName()) 821 .setArguments(bundle) 822 .setSourceMetricsCategory(getMetricsCategory()) 823 .launch(); 824 } 825 826 @VisibleForTesting createLongPressWifiEntryPreference(WifiEntry wifiEntry)827 LongPressWifiEntryPreference createLongPressWifiEntryPreference(WifiEntry wifiEntry) { 828 return new LongPressWifiEntryPreference(getPrefContext(), wifiEntry, this); 829 } 830 launchAddNetworkFragment()831 private void launchAddNetworkFragment() { 832 new SubSettingLauncher(getContext()) 833 .setTitleRes(R.string.wifi_add_network) 834 .setDestination(AddNetworkFragment.class.getName()) 835 .setSourceMetricsCategory(getMetricsCategory()) 836 .setResultListener(this, ADD_NETWORK_REQUEST) 837 .launch(); 838 } 839 840 /** Removes all preferences and hide the {@link #mConnectedWifiEntryPreferenceCategory}. */ removeConnectedWifiEntryPreference()841 private void removeConnectedWifiEntryPreference() { 842 mConnectedWifiEntryPreferenceCategory.removeAll(); 843 mConnectedWifiEntryPreferenceCategory.setVisible(false); 844 } 845 removeWifiEntryPreference()846 private void removeWifiEntryPreference() { 847 mWifiEntryPreferenceCategory.removeAll(); 848 mWifiEntryPreferenceCategory.setVisible(false); 849 } 850 851 @VisibleForTesting setAdditionalSettingsSummaries()852 void setAdditionalSettingsSummaries() { 853 mConfigureWifiSettingsPreference.setSummary(getString( 854 isWifiWakeupEnabled() 855 ? R.string.wifi_configure_settings_preference_summary_wakeup_on 856 : R.string.wifi_configure_settings_preference_summary_wakeup_off)); 857 858 final int numSavedNetworks = mWifiPickerTracker.getNumSavedNetworks(); 859 final int numSavedSubscriptions = mWifiPickerTracker.getNumSavedSubscriptions(); 860 if (numSavedNetworks + numSavedSubscriptions > 0) { 861 mSavedNetworksPreference.setVisible(true); 862 mSavedNetworksPreference.setSummary( 863 getSavedNetworkSettingsSummaryText(numSavedNetworks, numSavedSubscriptions)); 864 } else { 865 mSavedNetworksPreference.setVisible(false); 866 } 867 } 868 getSavedNetworkSettingsSummaryText( int numSavedNetworks, int numSavedSubscriptions)869 private String getSavedNetworkSettingsSummaryText( 870 int numSavedNetworks, int numSavedSubscriptions) { 871 if (numSavedSubscriptions == 0) { 872 return getResources().getQuantityString(R.plurals.wifi_saved_access_points_summary, 873 numSavedNetworks, numSavedNetworks); 874 } else if (numSavedNetworks == 0) { 875 return getResources().getQuantityString( 876 R.plurals.wifi_saved_passpoint_access_points_summary, 877 numSavedSubscriptions, numSavedSubscriptions); 878 } else { 879 final int numTotalEntries = numSavedNetworks + numSavedSubscriptions; 880 return getResources().getQuantityString(R.plurals.wifi_saved_all_access_points_summary, 881 numTotalEntries, numTotalEntries); 882 } 883 } 884 isWifiWakeupEnabled()885 private boolean isWifiWakeupEnabled() { 886 final Context context = getContext(); 887 final PowerManager powerManager = context.getSystemService(PowerManager.class); 888 final ContentResolver contentResolver = context.getContentResolver(); 889 return mWifiManager.isAutoWakeupEnabled() 890 && mWifiManager.isScanAlwaysAvailable() 891 && Settings.Global.getInt(contentResolver, 892 Settings.Global.AIRPLANE_MODE_ON, 0) == 0 893 && !powerManager.isPowerSaveMode(); 894 } 895 setOffMessage()896 private void setOffMessage() { 897 final CharSequence title = getText(R.string.wifi_empty_list_wifi_off); 898 // Don't use WifiManager.isScanAlwaysAvailable() to check the Wi-Fi scanning mode. Instead, 899 // read the system settings directly. Because when the device is in Airplane mode, even if 900 // Wi-Fi scanning mode is on, WifiManager.isScanAlwaysAvailable() still returns "off". 901 // TODO(b/149421497): Fix this? 902 final boolean wifiScanningMode = mWifiManager.isScanAlwaysAvailable(); 903 final CharSequence description = wifiScanningMode ? getText(R.string.wifi_scan_notify_text) 904 : getText(R.string.wifi_scan_notify_text_scanning_off); 905 final LinkifyUtils.OnClickListener clickListener = 906 () -> new SubSettingLauncher(getContext()) 907 .setDestination(ScanningSettings.class.getName()) 908 .setTitleRes(R.string.location_scanning_screen_title) 909 .setSourceMetricsCategory(getMetricsCategory()) 910 .launch(); 911 mStatusMessagePreference.setText(title, description, clickListener); 912 removeConnectedWifiEntryPreference(); 913 removeWifiEntryPreference(); 914 mStatusMessagePreference.setVisible(true); 915 } 916 addMessagePreference(int messageId)917 private void addMessagePreference(int messageId) { 918 mStatusMessagePreference.setTitle(messageId); 919 mStatusMessagePreference.setVisible(true); 920 921 } 922 setProgressBarVisible(boolean visible)923 protected void setProgressBarVisible(boolean visible) { 924 if (mProgressHeader != null) { 925 mProgressHeader.setVisibility(visible ? View.VISIBLE : View.GONE); 926 } 927 } 928 929 @VisibleForTesting handleAddNetworkRequest(int result, Intent data)930 void handleAddNetworkRequest(int result, Intent data) { 931 if (result == Activity.RESULT_OK) { 932 handleAddNetworkSubmitEvent(data); 933 } 934 } 935 handleAddNetworkSubmitEvent(Intent data)936 private void handleAddNetworkSubmitEvent(Intent data) { 937 final WifiConfiguration wifiConfiguration = data.getParcelableExtra( 938 AddNetworkFragment.WIFI_CONFIG_KEY); 939 if (wifiConfiguration != null) { 940 mWifiManager.save(wifiConfiguration, mSaveListener); 941 } 942 } 943 944 /** 945 * Called when "add network" button is pressed. 946 */ onAddNetworkPressed()947 private void onAddNetworkPressed() { 948 launchAddNetworkFragment(); 949 } 950 951 @Override getHelpResource()952 public int getHelpResource() { 953 return R.string.help_url_wifi; 954 } 955 956 /** 957 * Renames/replaces "Next" button when appropriate. "Next" button usually exists in 958 * Wi-Fi setup screens, not in usual wifi settings screen. 959 * 960 * @param enabled true when the device is connected to a wifi network. 961 */ 962 @VisibleForTesting changeNextButtonState(boolean enabled)963 void changeNextButtonState(boolean enabled) { 964 if (mEnableNextOnConnection && hasNextButton()) { 965 getNextButton().setEnabled(enabled); 966 } 967 } 968 969 @Override onForget(WifiDialog2 dialog)970 public void onForget(WifiDialog2 dialog) { 971 forget(dialog.getWifiEntry()); 972 } 973 974 @Override onSubmit(WifiDialog2 dialog)975 public void onSubmit(WifiDialog2 dialog) { 976 final int dialogMode = dialog.getMode(); 977 final WifiConfiguration config = dialog.getController().getConfig(); 978 final WifiEntry wifiEntry = dialog.getWifiEntry(); 979 980 if (dialogMode == WifiConfigUiBase2.MODE_MODIFY) { 981 if (config == null) { 982 Toast.makeText(getContext(), R.string.wifi_failed_save_message, 983 Toast.LENGTH_SHORT).show(); 984 } else { 985 mWifiManager.save(config, mSaveListener); 986 } 987 } else if (dialogMode == WifiConfigUiBase2.MODE_CONNECT 988 || (dialogMode == WifiConfigUiBase2.MODE_VIEW && wifiEntry.canConnect())) { 989 if (config == null) { 990 connect(wifiEntry, false /* editIfNoConfig */, 991 false /* fullScreenEdit*/); 992 } else { 993 mWifiManager.connect(config, new WifiConnectActionListener()); 994 } 995 } 996 } 997 998 @Override onScan(WifiDialog2 dialog, String ssid)999 public void onScan(WifiDialog2 dialog, String ssid) { 1000 // Launch QR code scanner to join a network. 1001 startActivityForResult(WifiDppUtils.getEnrolleeQrCodeScannerIntent(ssid), 1002 REQUEST_CODE_WIFI_DPP_ENROLLEE_QR_CODE_SCANNER); 1003 } 1004 forget(WifiEntry wifiEntry)1005 private void forget(WifiEntry wifiEntry) { 1006 mMetricsFeatureProvider.action(getActivity(), SettingsEnums.ACTION_WIFI_FORGET); 1007 wifiEntry.forget(null /* callback */); 1008 } 1009 1010 @VisibleForTesting connect(WifiEntry wifiEntry, boolean editIfNoConfig, boolean fullScreenEdit)1011 void connect(WifiEntry wifiEntry, boolean editIfNoConfig, boolean fullScreenEdit) { 1012 mMetricsFeatureProvider.action(getActivity(), SettingsEnums.ACTION_WIFI_CONNECT, 1013 wifiEntry.isSaved()); 1014 1015 // If it's an unsaved secure WifiEntry, it will callback 1016 // ConnectCallback#onConnectResult with ConnectCallback#CONNECT_STATUS_FAILURE_NO_CONFIG 1017 wifiEntry.connect(new WifiEntryConnectCallback(wifiEntry, editIfNoConfig, 1018 fullScreenEdit)); 1019 } 1020 1021 private class WifiConnectActionListener implements WifiManager.ActionListener { 1022 @Override onSuccess()1023 public void onSuccess() { 1024 mClickedConnect = true; 1025 } 1026 1027 @Override onFailure(int reason)1028 public void onFailure(int reason) { 1029 if (isFinishingOrDestroyed()) { 1030 return; 1031 } 1032 Toast.makeText(getContext(), R.string.wifi_failed_connect_message, Toast.LENGTH_SHORT) 1033 .show(); 1034 } 1035 }; 1036 1037 public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 1038 new BaseSearchIndexProvider(R.xml.wifi_settings2) { 1039 @Override 1040 public List<String> getNonIndexableKeys(Context context) { 1041 final List<String> keys = super.getNonIndexableKeys(context); 1042 1043 final WifiManager wifiManager = context.getSystemService(WifiManager.class); 1044 if (WifiSavedConfigUtils.getAllConfigsCount(context, wifiManager) == 0) { 1045 keys.add(PREF_KEY_SAVED_NETWORKS); 1046 } 1047 1048 if (!DataUsageUtils.hasWifiRadio(context)) { 1049 keys.add(PREF_KEY_DATA_USAGE); 1050 } 1051 return keys; 1052 } 1053 }; 1054 1055 private class WifiEntryConnectCallback implements ConnectCallback { 1056 final WifiEntry mConnectWifiEntry; 1057 final boolean mEditIfNoConfig; 1058 final boolean mFullScreenEdit; 1059 WifiEntryConnectCallback(WifiEntry connectWifiEntry, boolean editIfNoConfig, boolean fullScreenEdit)1060 WifiEntryConnectCallback(WifiEntry connectWifiEntry, boolean editIfNoConfig, 1061 boolean fullScreenEdit) { 1062 mConnectWifiEntry = connectWifiEntry; 1063 mEditIfNoConfig = editIfNoConfig; 1064 mFullScreenEdit = fullScreenEdit; 1065 } 1066 1067 @Override onConnectResult(@onnectStatus int status)1068 public void onConnectResult(@ConnectStatus int status) { 1069 if (isFinishingOrDestroyed()) { 1070 return; 1071 } 1072 1073 if (status == ConnectCallback.CONNECT_STATUS_SUCCESS) { 1074 mClickedConnect = true; 1075 } else if (status == ConnectCallback.CONNECT_STATUS_FAILURE_NO_CONFIG) { 1076 if (mEditIfNoConfig) { 1077 // Edit an unsaved secure Wi-Fi network. 1078 if (mFullScreenEdit) { 1079 launchConfigNewNetworkFragment(mConnectWifiEntry); 1080 } else { 1081 showDialog(mConnectWifiEntry, WifiConfigUiBase2.MODE_CONNECT); 1082 } 1083 } 1084 } else if (status == CONNECT_STATUS_FAILURE_UNKNOWN) { 1085 Toast.makeText(getContext(), R.string.wifi_failed_connect_message, 1086 Toast.LENGTH_SHORT).show(); 1087 } 1088 } 1089 } 1090 launchConfigNewNetworkFragment(WifiEntry wifiEntry)1091 private void launchConfigNewNetworkFragment(WifiEntry wifiEntry) { 1092 final Bundle bundle = new Bundle(); 1093 bundle.putString(WifiNetworkDetailsFragment2.KEY_CHOSEN_WIFIENTRY_KEY, 1094 wifiEntry.getKey()); 1095 new SubSettingLauncher(getContext()) 1096 .setTitleText(wifiEntry.getTitle()) 1097 .setDestination(ConfigureWifiEntryFragment.class.getName()) 1098 .setArguments(bundle) 1099 .setSourceMetricsCategory(getMetricsCategory()) 1100 .setResultListener(WifiSettings2.this, CONFIG_NETWORK_REQUEST) 1101 .launch(); 1102 } 1103 1104 /** Helper method to return whether a WifiEntry is disabled due to a wrong password */ isDisabledByWrongPassword(WifiEntry wifiEntry)1105 private static boolean isDisabledByWrongPassword(WifiEntry wifiEntry) { 1106 WifiConfiguration config = wifiEntry.getWifiConfiguration(); 1107 if (config == null) { 1108 return false; 1109 } 1110 WifiConfiguration.NetworkSelectionStatus networkStatus = 1111 config.getNetworkSelectionStatus(); 1112 if (networkStatus == null 1113 || networkStatus.getNetworkSelectionStatus() == NETWORK_SELECTION_ENABLED) { 1114 return false; 1115 } 1116 int reason = networkStatus.getNetworkSelectionDisableReason(); 1117 return WifiConfiguration.NetworkSelectionStatus.DISABLED_BY_WRONG_PASSWORD == reason; 1118 } 1119 1120 @VisibleForTesting openSubscriptionHelpPage(WifiEntry wifiEntry)1121 void openSubscriptionHelpPage(WifiEntry wifiEntry) { 1122 final Intent intent = getHelpIntent(getContext(), wifiEntry.getHelpUriString()); 1123 if (intent != null) { 1124 try { 1125 startActivityForResult(intent, MANAGE_SUBSCRIPTION); 1126 } catch (ActivityNotFoundException e) { 1127 Log.e(TAG, "Activity was not found for intent, " + intent.toString()); 1128 } 1129 } 1130 } 1131 1132 @VisibleForTesting getHelpIntent(Context context, String helpUrlString)1133 Intent getHelpIntent(Context context, String helpUrlString) { 1134 return HelpUtils.getHelpIntent(context, helpUrlString, context.getClass().getName()); 1135 } 1136 } 1137