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