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