WifiSettings.java revision 5ead6b92fdcfd5da4841509be534efa6264a4ccc
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 static android.net.wifi.WifiConfiguration.INVALID_NETWORK_ID; 20import static android.os.UserManager.DISALLOW_CONFIG_WIFI; 21 22import android.app.Activity; 23import android.app.ActivityManager; 24import android.app.Dialog; 25import android.content.BroadcastReceiver; 26import android.content.Context; 27import android.content.DialogInterface; 28import android.content.Intent; 29import android.content.IntentFilter; 30import android.content.res.Resources; 31import android.content.res.TypedArray; 32import android.location.LocationManager; 33import android.net.ConnectivityManager; 34import android.net.NetworkInfo; 35import android.net.NetworkInfo.DetailedState; 36import android.net.NetworkInfo.State; 37import android.net.wifi.ScanResult; 38import android.net.wifi.WifiConfiguration; 39import android.net.wifi.WifiInfo; 40import android.net.wifi.WifiManager; 41import android.net.wifi.WpsInfo; 42import android.os.Bundle; 43import android.os.Handler; 44import android.os.Message; 45import android.os.UserHandle; 46import android.preference.Preference; 47import android.preference.PreferenceScreen; 48import android.util.Log; 49import android.view.ContextMenu; 50import android.view.ContextMenu.ContextMenuInfo; 51import android.view.Menu; 52import android.view.MenuInflater; 53import android.view.MenuItem; 54import android.view.View; 55import android.widget.AdapterView.AdapterContextMenuInfo; 56import android.widget.TextView; 57import android.widget.Toast; 58 59import com.android.settings.R; 60import com.android.settings.RestrictedSettingsFragment; 61import com.android.settings.SettingsActivity; 62import com.android.settings.search.BaseSearchIndexProvider; 63import com.android.settings.search.Indexable; 64import com.android.settings.search.SearchIndexableRaw; 65 66import java.util.ArrayList; 67import java.util.Collection; 68import java.util.Collections; 69import java.util.HashMap; 70import java.util.List; 71import java.util.concurrent.atomic.AtomicBoolean; 72 73/** 74 * Two types of UI are provided here. 75 * 76 * The first is for "usual Settings", appearing as any other Setup fragment. 77 * 78 * The second is for Setup Wizard, with a simplified interface that hides the action bar 79 * and menus. 80 */ 81public class WifiSettings extends RestrictedSettingsFragment 82 implements DialogInterface.OnClickListener, Indexable { 83 84 private static final String TAG = "WifiSettings"; 85 86 /* package */ static final int MENU_ID_WPS_PBC = Menu.FIRST; 87 private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1; 88 private static final int MENU_ID_SAVED_NETWORK = Menu.FIRST + 2; 89 /* package */ static final int MENU_ID_ADD_NETWORK = Menu.FIRST + 3; 90 private static final int MENU_ID_ADVANCED = Menu.FIRST + 4; 91 private static final int MENU_ID_SCAN = Menu.FIRST + 5; 92 private static final int MENU_ID_CONNECT = Menu.FIRST + 6; 93 private static final int MENU_ID_FORGET = Menu.FIRST + 7; 94 private static final int MENU_ID_MODIFY = Menu.FIRST + 8; 95 private static final int MENU_ID_WRITE_NFC = Menu.FIRST + 9; 96 97 public static final int WIFI_DIALOG_ID = 1; 98 /* package */ static final int WPS_PBC_DIALOG_ID = 2; 99 private static final int WPS_PIN_DIALOG_ID = 3; 100 private static final int WRITE_NFC_DIALOG_ID = 6; 101 102 // Combo scans can take 5-6s to complete - set to 10s. 103 private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000; 104 105 // Instance state keys 106 private static final String SAVE_DIALOG_EDIT_MODE = "edit_mode"; 107 private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state"; 108 109 private static boolean savedNetworksExist; 110 111 private final IntentFilter mFilter; 112 private final BroadcastReceiver mReceiver; 113 private final Scanner mScanner; 114 115 /* package */ 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 NetworkInfo mLastNetworkInfo; 125 private WifiInfo mLastInfo; 126 127 private final AtomicBoolean mConnected = new AtomicBoolean(false); 128 129 private WifiDialog mDialog; 130 private WriteWifiConfigToNfcDialog mWifiToNfcDialog; 131 132 private TextView mEmptyView; 133 134 // this boolean extra specifies whether to disable the Next button when not connected. Used by 135 // account creation outside of setup wizard. 136 private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect"; 137 // This string extra specifies a network to open the connect dialog on, so the user can enter 138 // network credentials. This is used by quick settings for secured networks. 139 private static final String EXTRA_START_CONNECT_SSID = "wifi_start_connect_ssid"; 140 141 // should Next button only be enabled when we have a connection? 142 private boolean mEnableNextOnConnection; 143 144 // Save the dialog details 145 private boolean mDlgEdit; 146 private AccessPoint mDlgAccessPoint; 147 private Bundle mAccessPointSavedState; 148 149 /** verbose logging flag. this flag is set thru developer debugging options 150 * and used so as to assist with in-the-field WiFi connectivity debugging */ 151 public static int mVerboseLogging = 0; 152 153 /* End of "used in Wifi Setup context" */ 154 155 /** A restricted multimap for use in constructAccessPoints */ 156 private static class Multimap<K,V> { 157 private final HashMap<K,List<V>> store = new HashMap<K,List<V>>(); 158 /** retrieve a non-null list of values with key K */ 159 List<V> getAll(K key) { 160 List<V> values = store.get(key); 161 return values != null ? values : Collections.<V>emptyList(); 162 } 163 164 void put(K key, V val) { 165 List<V> curVals = store.get(key); 166 if (curVals == null) { 167 curVals = new ArrayList<V>(3); 168 store.put(key, curVals); 169 } 170 curVals.add(val); 171 } 172 } 173 174 private static class Scanner extends Handler { 175 private int mRetry = 0; 176 private WifiSettings mWifiSettings = null; 177 178 Scanner(WifiSettings wifiSettings) { 179 mWifiSettings = wifiSettings; 180 } 181 182 void resume() { 183 if (!hasMessages(0)) { 184 sendEmptyMessage(0); 185 } 186 } 187 188 void forceScan() { 189 removeMessages(0); 190 sendEmptyMessage(0); 191 } 192 193 void pause() { 194 mRetry = 0; 195 removeMessages(0); 196 } 197 198 @Override 199 public void handleMessage(Message message) { 200 if (mWifiSettings.mWifiManager.startScan()) { 201 mRetry = 0; 202 } else if (++mRetry >= 3) { 203 mRetry = 0; 204 Activity activity = mWifiSettings.getActivity(); 205 if (activity != null) { 206 Toast.makeText(activity, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show(); 207 } 208 return; 209 } 210 sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS); 211 } 212 } 213 214 public WifiSettings() { 215 super(DISALLOW_CONFIG_WIFI); 216 mFilter = new IntentFilter(); 217 mFilter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION); 218 mFilter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION); 219 mFilter.addAction(WifiManager.NETWORK_IDS_CHANGED_ACTION); 220 mFilter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION); 221 mFilter.addAction(WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION); 222 mFilter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION); 223 mFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); 224 mFilter.addAction(WifiManager.RSSI_CHANGED_ACTION); 225 226 mReceiver = new BroadcastReceiver() { 227 @Override 228 public void onReceive(Context context, Intent intent) { 229 handleEvent(intent); 230 } 231 }; 232 233 mScanner = new Scanner(this); 234 } 235 236 @Override 237 public void onActivityCreated(Bundle savedInstanceState) { 238 super.onActivityCreated(savedInstanceState); 239 240 mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); 241 242 mConnectListener = new WifiManager.ActionListener() { 243 @Override 244 public void onSuccess() { 245 } 246 @Override 247 public void onFailure(int reason) { 248 Activity activity = getActivity(); 249 if (activity != null) { 250 Toast.makeText(activity, 251 R.string.wifi_failed_connect_message, 252 Toast.LENGTH_SHORT).show(); 253 } 254 } 255 }; 256 257 mSaveListener = new WifiManager.ActionListener() { 258 @Override 259 public void onSuccess() { 260 } 261 @Override 262 public void onFailure(int reason) { 263 Activity activity = getActivity(); 264 if (activity != null) { 265 Toast.makeText(activity, 266 R.string.wifi_failed_save_message, 267 Toast.LENGTH_SHORT).show(); 268 } 269 } 270 }; 271 272 mForgetListener = new WifiManager.ActionListener() { 273 @Override 274 public void onSuccess() { 275 } 276 @Override 277 public void onFailure(int reason) { 278 Activity activity = getActivity(); 279 if (activity != null) { 280 Toast.makeText(activity, 281 R.string.wifi_failed_forget_message, 282 Toast.LENGTH_SHORT).show(); 283 } 284 } 285 }; 286 287 if (savedInstanceState != null) { 288 mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE); 289 if (savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) { 290 mAccessPointSavedState = 291 savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE); 292 } 293 } 294 295 // if we're supposed to enable/disable the Next button based on our current connection 296 // state, start it off in the right state 297 Intent intent = getActivity().getIntent(); 298 mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false); 299 300 if (mEnableNextOnConnection) { 301 if (hasNextButton()) { 302 final ConnectivityManager connectivity = (ConnectivityManager) 303 getActivity().getSystemService(Context.CONNECTIVITY_SERVICE); 304 if (connectivity != null) { 305 NetworkInfo info = connectivity.getNetworkInfo( 306 ConnectivityManager.TYPE_WIFI); 307 changeNextButtonState(info.isConnected()); 308 } 309 } 310 } 311 312 addPreferencesFromResource(R.xml.wifi_settings); 313 314 mEmptyView = initEmptyView(); 315 registerForContextMenu(getListView()); 316 setHasOptionsMenu(true); 317 318 if (intent.hasExtra(EXTRA_START_CONNECT_SSID)) { 319 String ssid = intent.getStringExtra(EXTRA_START_CONNECT_SSID); 320 updateAccessPoints(); 321 PreferenceScreen preferenceScreen = getPreferenceScreen(); 322 for (int i = 0; i < preferenceScreen.getPreferenceCount(); i++) { 323 Preference preference = preferenceScreen.getPreference(i); 324 if (preference instanceof AccessPoint) { 325 AccessPoint accessPoint = (AccessPoint) preference; 326 if (ssid.equals(accessPoint.ssid) && accessPoint.networkId == -1 327 && accessPoint.security != AccessPoint.SECURITY_NONE) { 328 onPreferenceTreeClick(preferenceScreen, preference); 329 break; 330 } 331 } 332 } 333 } 334 } 335 336 @Override 337 public void onDestroyView() { 338 super.onDestroyView(); 339 340 if (mWifiEnabler != null) { 341 mWifiEnabler.teardownSwitchBar(); 342 } 343 } 344 345 @Override 346 public void onStart() { 347 super.onStart(); 348 349 // On/off switch is hidden for Setup Wizard (returns null) 350 mWifiEnabler = createWifiEnabler(); 351 } 352 353 /** 354 * @return new WifiEnabler or null (as overridden by WifiSettingsForSetupWizard) 355 */ 356 /* package */ WifiEnabler createWifiEnabler() { 357 final SettingsActivity activity = (SettingsActivity) getActivity(); 358 return new WifiEnabler(activity, activity.getSwitchBar()); 359 } 360 361 @Override 362 public void onResume() { 363 final Activity activity = getActivity(); 364 super.onResume(); 365 if (mWifiEnabler != null) { 366 mWifiEnabler.resume(activity); 367 } 368 369 activity.registerReceiver(mReceiver, mFilter); 370 updateAccessPoints(); 371 } 372 373 @Override 374 public void onPause() { 375 super.onPause(); 376 if (mWifiEnabler != null) { 377 mWifiEnabler.pause(); 378 } 379 380 getActivity().unregisterReceiver(mReceiver); 381 mScanner.pause(); 382 } 383 384 @Override 385 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 386 // If the user is not allowed to configure wifi, do not show the menu. 387 if (isUiRestricted()) return; 388 389 addOptionsMenuItems(menu); 390 super.onCreateOptionsMenu(menu, inflater); 391 } 392 393 /** 394 * @param menu 395 */ 396 void addOptionsMenuItems(Menu menu) { 397 final boolean wifiIsEnabled = mWifiManager.isWifiEnabled(); 398 TypedArray ta = getActivity().getTheme().obtainStyledAttributes( 399 new int[] {R.attr.ic_menu_add, R.attr.ic_wps}); 400 menu.add(Menu.NONE, MENU_ID_ADD_NETWORK, 0, R.string.wifi_add_network) 401 .setIcon(ta.getDrawable(0)) 402 .setEnabled(wifiIsEnabled) 403 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 404 if (savedNetworksExist) { 405 menu.add(Menu.NONE, MENU_ID_SAVED_NETWORK, 0, R.string.wifi_saved_access_points_label) 406 .setIcon(ta.getDrawable(0)) 407 .setEnabled(wifiIsEnabled) 408 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 409 } 410 menu.add(Menu.NONE, MENU_ID_SCAN, 0, R.string.menu_stats_refresh) 411 .setEnabled(wifiIsEnabled) 412 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 413 menu.add(Menu.NONE, MENU_ID_ADVANCED, 0, R.string.wifi_menu_advanced) 414 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 415 ta.recycle(); 416 } 417 418 @Override 419 public void onSaveInstanceState(Bundle outState) { 420 super.onSaveInstanceState(outState); 421 422 // If the dialog is showing, save its state. 423 if (mDialog != null && mDialog.isShowing()) { 424 outState.putBoolean(SAVE_DIALOG_EDIT_MODE, mDlgEdit); 425 if (mDlgAccessPoint != null) { 426 mAccessPointSavedState = new Bundle(); 427 mDlgAccessPoint.saveWifiState(mAccessPointSavedState); 428 outState.putBundle(SAVE_DIALOG_ACCESS_POINT_STATE, mAccessPointSavedState); 429 } 430 } 431 } 432 433 @Override 434 public boolean onOptionsItemSelected(MenuItem item) { 435 // If the user is not allowed to configure wifi, do not handle menu selections. 436 if (isUiRestricted()) return false; 437 438 switch (item.getItemId()) { 439 case MENU_ID_WPS_PBC: 440 showDialog(WPS_PBC_DIALOG_ID); 441 return true; 442 /* 443 case MENU_ID_P2P: 444 if (getActivity() instanceof SettingsActivity) { 445 ((SettingsActivity) getActivity()).startPreferencePanel( 446 WifiP2pSettings.class.getCanonicalName(), 447 null, 448 R.string.wifi_p2p_settings_title, null, 449 this, 0); 450 } else { 451 startFragment(this, WifiP2pSettings.class.getCanonicalName(), 452 R.string.wifi_p2p_settings_title, -1, null); 453 } 454 return true; 455 */ 456 case MENU_ID_WPS_PIN: 457 showDialog(WPS_PIN_DIALOG_ID); 458 return true; 459 case MENU_ID_SCAN: 460 if (mWifiManager.isWifiEnabled()) { 461 mScanner.forceScan(); 462 } 463 return true; 464 case MENU_ID_ADD_NETWORK: 465 if (mWifiManager.isWifiEnabled()) { 466 onAddNetworkPressed(); 467 } 468 return true; 469 case MENU_ID_SAVED_NETWORK: 470 if (getActivity() instanceof SettingsActivity) { 471 ((SettingsActivity) getActivity()).startPreferencePanel( 472 SavedAccessPointsWifiSettings.class.getCanonicalName(), null, 473 R.string.wifi_saved_access_points_titlebar, null, this, 0); 474 } else { 475 startFragment(this, SavedAccessPointsWifiSettings.class.getCanonicalName(), 476 R.string.wifi_saved_access_points_titlebar, 477 -1 /* Do not request a result */, null); 478 } 479 return true; 480 case MENU_ID_ADVANCED: 481 if (getActivity() instanceof SettingsActivity) { 482 ((SettingsActivity) getActivity()).startPreferencePanel( 483 AdvancedWifiSettings.class.getCanonicalName(), null, 484 R.string.wifi_advanced_titlebar, null, this, 0); 485 } else { 486 startFragment(this, AdvancedWifiSettings.class.getCanonicalName(), 487 R.string.wifi_advanced_titlebar, -1 /* Do not request a results */, 488 null); 489 } 490 return true; 491 } 492 return super.onOptionsItemSelected(item); 493 } 494 495 @Override 496 public void onCreateContextMenu(ContextMenu menu, View view, ContextMenuInfo info) { 497 if (info instanceof AdapterContextMenuInfo) { 498 Preference preference = (Preference) getListView().getItemAtPosition( 499 ((AdapterContextMenuInfo) info).position); 500 501 if (preference instanceof AccessPoint) { 502 mSelectedAccessPoint = (AccessPoint) preference; 503 menu.setHeaderTitle(mSelectedAccessPoint.ssid); 504 if (mSelectedAccessPoint.getLevel() != -1) { 505 if (mSelectedAccessPoint.getState() == null) { 506 menu.add(Menu.NONE, MENU_ID_CONNECT, 0, R.string.wifi_menu_connect); 507 } 508 } 509 510 if (ActivityManager.getCurrentUser() == UserHandle.USER_OWNER && 511 (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID || 512 (mSelectedAccessPoint.getNetworkInfo() != null && 513 mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED))) { 514 // Allow forgetting a network if the current user is the owner and either the 515 // network is saved or ephemerally connected. (In the latter case, "forget" 516 // blacklists the network so it won't be used again, ephemerally). 517 menu.add(Menu.NONE, MENU_ID_FORGET, 0, R.string.wifi_menu_forget); 518 } 519 if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) { 520 menu.add(Menu.NONE, MENU_ID_MODIFY, 0, R.string.wifi_menu_modify); 521 522 if (mSelectedAccessPoint.security != AccessPoint.SECURITY_NONE) { 523 // Only allow writing of NFC tags for password-protected networks. 524 menu.add(Menu.NONE, MENU_ID_WRITE_NFC, 0, R.string.wifi_menu_write_to_nfc); 525 } 526 } 527 } 528 } 529 } 530 531 @Override 532 public boolean onContextItemSelected(MenuItem item) { 533 if (mSelectedAccessPoint == null) { 534 return super.onContextItemSelected(item); 535 } 536 switch (item.getItemId()) { 537 case MENU_ID_CONNECT: { 538 if (mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) { 539 connect(mSelectedAccessPoint.networkId); 540 } else if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE) { 541 /** Bypass dialog for unsecured networks */ 542 mSelectedAccessPoint.generateOpenNetworkConfig(); 543 connect(mSelectedAccessPoint.getConfig()); 544 } else { 545 showDialog(mSelectedAccessPoint, true); 546 } 547 return true; 548 } 549 case MENU_ID_FORGET: { 550 forget(); 551 return true; 552 } 553 case MENU_ID_MODIFY: { 554 showDialog(mSelectedAccessPoint, true); 555 return true; 556 } 557 case MENU_ID_WRITE_NFC: 558 showDialog(WRITE_NFC_DIALOG_ID); 559 return true; 560 561 } 562 return super.onContextItemSelected(item); 563 } 564 565 @Override 566 public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { 567 if (preference instanceof AccessPoint) { 568 mSelectedAccessPoint = (AccessPoint) preference; 569 /** Bypass dialog for unsecured, unsaved, and inactive networks */ 570 if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE && 571 mSelectedAccessPoint.networkId == INVALID_NETWORK_ID && 572 !mSelectedAccessPoint.isActive()) { 573 mSelectedAccessPoint.generateOpenNetworkConfig(); 574 if (!savedNetworksExist) { 575 savedNetworksExist = true; 576 getActivity().invalidateOptionsMenu(); 577 } 578 connect(mSelectedAccessPoint.getConfig()); 579 } else { 580 showDialog(mSelectedAccessPoint, false); 581 } 582 } else { 583 return super.onPreferenceTreeClick(screen, preference); 584 } 585 return true; 586 } 587 588 private void showDialog(AccessPoint accessPoint, boolean edit) { 589 if (mDialog != null) { 590 removeDialog(WIFI_DIALOG_ID); 591 mDialog = null; 592 } 593 594 // Save the access point and edit mode 595 mDlgAccessPoint = accessPoint; 596 mDlgEdit = edit; 597 598 showDialog(WIFI_DIALOG_ID); 599 } 600 601 @Override 602 public Dialog onCreateDialog(int dialogId) { 603 switch (dialogId) { 604 case WIFI_DIALOG_ID: 605 AccessPoint ap = mDlgAccessPoint; // For manual launch 606 if (ap == null) { // For re-launch from saved state 607 if (mAccessPointSavedState != null) { 608 ap = new AccessPoint(getActivity(), mAccessPointSavedState); 609 // For repeated orientation changes 610 mDlgAccessPoint = ap; 611 // Reset the saved access point data 612 mAccessPointSavedState = null; 613 } 614 } 615 // If it's null, fine, it's for Add Network 616 mSelectedAccessPoint = ap; 617 mDialog = new WifiDialog(getActivity(), this, ap, mDlgEdit); 618 return mDialog; 619 case WPS_PBC_DIALOG_ID: 620 return new WpsDialog(getActivity(), WpsInfo.PBC); 621 case WPS_PIN_DIALOG_ID: 622 return new WpsDialog(getActivity(), WpsInfo.DISPLAY); 623 case WRITE_NFC_DIALOG_ID: 624 if (mSelectedAccessPoint != null) { 625 mWifiToNfcDialog = new WriteWifiConfigToNfcDialog( 626 getActivity(), mSelectedAccessPoint, mWifiManager); 627 return mWifiToNfcDialog; 628 } 629 630 } 631 return super.onCreateDialog(dialogId); 632 } 633 634 /** 635 * Shows the latest access points available with supplemental information like 636 * the strength of network and the security for it. 637 */ 638 private void updateAccessPoints() { 639 // Safeguard from some delayed event handling 640 if (getActivity() == null) return; 641 642 if (isUiRestricted()) { 643 addMessagePreference(R.string.wifi_empty_list_user_restricted); 644 return; 645 } 646 final int wifiState = mWifiManager.getWifiState(); 647 648 //when we update the screen, check if verbose logging has been turned on or off 649 mVerboseLogging = mWifiManager.getVerboseLoggingLevel(); 650 651 switch (wifiState) { 652 case WifiManager.WIFI_STATE_ENABLED: 653 // AccessPoints are automatically sorted with TreeSet. 654 final Collection<AccessPoint> accessPoints = 655 constructAccessPoints(getActivity(), mWifiManager, mLastInfo, 656 mLastNetworkInfo); 657 getPreferenceScreen().removeAll(); 658 if (accessPoints.size() == 0) { 659 addMessagePreference(R.string.wifi_empty_list_wifi_on); 660 } 661 662 for (AccessPoint accessPoint : accessPoints) { 663 // Ignore access points that are out of range. 664 if (accessPoint.getLevel() != -1) { 665 getPreferenceScreen().addPreference(accessPoint); 666 } 667 } 668 break; 669 670 case WifiManager.WIFI_STATE_ENABLING: 671 getPreferenceScreen().removeAll(); 672 break; 673 674 case WifiManager.WIFI_STATE_DISABLING: 675 addMessagePreference(R.string.wifi_stopping); 676 break; 677 678 case WifiManager.WIFI_STATE_DISABLED: 679 setOffMessage(); 680 break; 681 } 682 } 683 684 protected TextView initEmptyView() { 685 TextView emptyView = (TextView) getActivity().findViewById(android.R.id.empty); 686 getListView().setEmptyView(emptyView); 687 return emptyView; 688 } 689 690 private void setOffMessage() { 691 if (mEmptyView != null) { 692 mEmptyView.setText(R.string.wifi_empty_list_wifi_off); 693 if (android.provider.Settings.Global.getInt(getActivity().getContentResolver(), 694 android.provider.Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1) { 695 mEmptyView.append("\n\n"); 696 int resId; 697 if (android.provider.Settings.Secure.isLocationProviderEnabled( 698 getActivity().getContentResolver(), LocationManager.NETWORK_PROVIDER)) { 699 resId = R.string.wifi_scan_notify_text_location_on; 700 } else { 701 resId = R.string.wifi_scan_notify_text_location_off; 702 } 703 CharSequence charSeq = getText(resId); 704 mEmptyView.append(charSeq); 705 } 706 } 707 getPreferenceScreen().removeAll(); 708 } 709 710 private void addMessagePreference(int messageId) { 711 if (mEmptyView != null) mEmptyView.setText(messageId); 712 getPreferenceScreen().removeAll(); 713 } 714 715 /** Returns sorted list of access points */ 716 private static List<AccessPoint> constructAccessPoints(Context context, 717 WifiManager wifiManager, WifiInfo lastInfo, NetworkInfo lastNetworkInfo) { 718 ArrayList<AccessPoint> accessPoints = new ArrayList<AccessPoint>(); 719 /** Lookup table to more quickly update AccessPoints by only considering objects with the 720 * correct SSID. Maps SSID -> List of AccessPoints with the given SSID. */ 721 Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>(); 722 723 final List<WifiConfiguration> configs = wifiManager.getConfiguredNetworks(); 724 if (configs != null) { 725 // Update "Saved Networks" menu option. 726 if (savedNetworksExist != (configs.size() > 0)) { 727 savedNetworksExist = !savedNetworksExist; 728 if (context instanceof Activity) { 729 ((Activity) context).invalidateOptionsMenu(); 730 } 731 } 732 for (WifiConfiguration config : configs) { 733 if (config.selfAdded && config.numAssociation == 0) { 734 continue; 735 } 736 AccessPoint accessPoint = new AccessPoint(context, config); 737 if (lastInfo != null && lastNetworkInfo != null) { 738 accessPoint.update(lastInfo, lastNetworkInfo); 739 } 740 accessPoints.add(accessPoint); 741 apMap.put(accessPoint.ssid, accessPoint); 742 } 743 } 744 745 final List<ScanResult> results = wifiManager.getScanResults(); 746 if (results != null) { 747 for (ScanResult result : results) { 748 // Ignore hidden and ad-hoc networks. 749 if (result.SSID == null || result.SSID.length() == 0 || 750 result.capabilities.contains("[IBSS]")) { 751 continue; 752 } 753 754 boolean found = false; 755 for (AccessPoint accessPoint : apMap.getAll(result.SSID)) { 756 if (accessPoint.update(result)) 757 found = true; 758 } 759 if (!found) { 760 AccessPoint accessPoint = new AccessPoint(context, result); 761 if (lastInfo != null && lastNetworkInfo != null) { 762 accessPoint.update(lastInfo, lastNetworkInfo); 763 } 764 accessPoints.add(accessPoint); 765 apMap.put(accessPoint.ssid, accessPoint); 766 } 767 } 768 } 769 770 // Pre-sort accessPoints to speed preference insertion 771 Collections.sort(accessPoints); 772 return accessPoints; 773 } 774 775 private void handleEvent(Intent intent) { 776 String action = intent.getAction(); 777 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { 778 updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 779 WifiManager.WIFI_STATE_UNKNOWN)); 780 } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) || 781 WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) || 782 WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) { 783 updateAccessPoints(); 784 } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) { 785 NetworkInfo info = (NetworkInfo) intent.getParcelableExtra( 786 WifiManager.EXTRA_NETWORK_INFO); 787 mConnected.set(info.isConnected()); 788 changeNextButtonState(info.isConnected()); 789 updateAccessPoints(); 790 updateNetworkInfo(info); 791 } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) { 792 updateNetworkInfo(null); 793 } 794 } 795 796 private void updateNetworkInfo(NetworkInfo networkInfo) { 797 /* sticky broadcasts can call this when wifi is disabled */ 798 if (!mWifiManager.isWifiEnabled()) { 799 mScanner.pause(); 800 return; 801 } 802 803 if (networkInfo != null && 804 networkInfo.getDetailedState() == DetailedState.OBTAINING_IPADDR) { 805 mScanner.pause(); 806 } else { 807 mScanner.resume(); 808 } 809 810 mLastInfo = mWifiManager.getConnectionInfo(); 811 if (networkInfo != null) { 812 mLastNetworkInfo = networkInfo; 813 } 814 815 for (int i = getPreferenceScreen().getPreferenceCount() - 1; i >= 0; --i) { 816 // Maybe there's a WifiConfigPreference 817 Preference preference = getPreferenceScreen().getPreference(i); 818 if (preference instanceof AccessPoint) { 819 final AccessPoint accessPoint = (AccessPoint) preference; 820 accessPoint.update(mLastInfo, mLastNetworkInfo); 821 } 822 } 823 } 824 825 private void updateWifiState(int state) { 826 Activity activity = getActivity(); 827 if (activity != null) { 828 activity.invalidateOptionsMenu(); 829 } 830 831 switch (state) { 832 case WifiManager.WIFI_STATE_ENABLED: 833 mScanner.resume(); 834 return; // not break, to avoid the call to pause() below 835 836 case WifiManager.WIFI_STATE_ENABLING: 837 addMessagePreference(R.string.wifi_starting); 838 break; 839 840 case WifiManager.WIFI_STATE_DISABLED: 841 setOffMessage(); 842 break; 843 } 844 845 mLastInfo = null; 846 mLastNetworkInfo = null; 847 mScanner.pause(); 848 } 849 850 /** 851 * Renames/replaces "Next" button when appropriate. "Next" button usually exists in 852 * Wifi setup screens, not in usual wifi settings screen. 853 * 854 * @param enabled true when the device is connected to a wifi network. 855 */ 856 private void changeNextButtonState(boolean enabled) { 857 if (mEnableNextOnConnection && hasNextButton()) { 858 getNextButton().setEnabled(enabled); 859 } 860 } 861 862 @Override 863 public void onClick(DialogInterface dialogInterface, int button) { 864 if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) { 865 forget(); 866 } else if (button == WifiDialog.BUTTON_SUBMIT) { 867 if (mDialog != null) { 868 submit(mDialog.getController()); 869 } 870 } 871 } 872 873 /* package */ void submit(WifiConfigController configController) { 874 875 final WifiConfiguration config = configController.getConfig(); 876 877 if (config == null) { 878 if (mSelectedAccessPoint != null 879 && mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) { 880 connect(mSelectedAccessPoint.networkId); 881 } 882 } else if (config.networkId != INVALID_NETWORK_ID) { 883 if (mSelectedAccessPoint != null) { 884 mWifiManager.save(config, mSaveListener); 885 } 886 } else { 887 if (configController.isEdit()) { 888 mWifiManager.save(config, mSaveListener); 889 } else { 890 connect(config); 891 } 892 } 893 894 if (mWifiManager.isWifiEnabled()) { 895 mScanner.resume(); 896 } 897 updateAccessPoints(); 898 } 899 900 /* package */ void forget() { 901 if (mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) { 902 if (mSelectedAccessPoint.getNetworkInfo().getState() != State.DISCONNECTED) { 903 // Network is active but has no network ID - must be ephemeral. 904 mWifiManager.disableEphemeralNetwork( 905 AccessPoint.convertToQuotedString(mSelectedAccessPoint.ssid)); 906 } else { 907 // Should not happen, but a monkey seems to trigger it 908 Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig()); 909 return; 910 } 911 } else { 912 mWifiManager.forget(mSelectedAccessPoint.networkId, mForgetListener); 913 } 914 915 916 if (mWifiManager.isWifiEnabled()) { 917 mScanner.resume(); 918 } 919 updateAccessPoints(); 920 921 // We need to rename/replace "Next" button in wifi setup context. 922 changeNextButtonState(false); 923 } 924 925 protected void connect(final WifiConfiguration config) { 926 mWifiManager.connect(config, mConnectListener); 927 } 928 929 protected void connect(final int networkId) { 930 mWifiManager.connect(networkId, mConnectListener); 931 } 932 933 /** 934 * Refreshes acccess points and ask Wifi module to scan networks again. 935 */ 936 /* package */ void refreshAccessPoints() { 937 if (mWifiManager.isWifiEnabled()) { 938 mScanner.resume(); 939 } 940 941 getPreferenceScreen().removeAll(); 942 } 943 944 /** 945 * Called when "add network" button is pressed. 946 */ 947 /* package */ void onAddNetworkPressed() { 948 // No exact access point is selected. 949 mSelectedAccessPoint = null; 950 showDialog(null, true); 951 } 952 953 /* package */ int getAccessPointsCount() { 954 final boolean wifiIsEnabled = mWifiManager.isWifiEnabled(); 955 if (wifiIsEnabled) { 956 return getPreferenceScreen().getPreferenceCount(); 957 } else { 958 return 0; 959 } 960 } 961 962 /** 963 * Requests wifi module to pause wifi scan. May be ignored when the module is disabled. 964 */ 965 /* package */ void pauseWifiScan() { 966 if (mWifiManager.isWifiEnabled()) { 967 mScanner.pause(); 968 } 969 } 970 971 /** 972 * Requests wifi module to resume wifi scan. May be ignored when the module is disabled. 973 */ 974 /* package */ void resumeWifiScan() { 975 if (mWifiManager.isWifiEnabled()) { 976 mScanner.resume(); 977 } 978 } 979 980 @Override 981 protected int getHelpResource() { 982 return R.string.help_url_wifi; 983 } 984 985 public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 986 new BaseSearchIndexProvider() { 987 @Override 988 public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { 989 final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>(); 990 final Resources res = context.getResources(); 991 992 // Add fragment title 993 SearchIndexableRaw data = new SearchIndexableRaw(context); 994 data.title = res.getString(R.string.wifi_settings); 995 data.screenTitle = res.getString(R.string.wifi_settings); 996 data.keywords = res.getString(R.string.keywords_wifi); 997 result.add(data); 998 999 // Add available Wi-Fi access points 1000 WifiManager wifiManager = 1001 (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 1002 final Collection<AccessPoint> accessPoints = 1003 constructAccessPoints(context, wifiManager, null, null); 1004 for (AccessPoint accessPoint : accessPoints) { 1005 // We are indexing only the saved Wi-Fi networks. 1006 if (accessPoint.getConfig() == null) continue; 1007 data = new SearchIndexableRaw(context); 1008 data.title = accessPoint.getTitle().toString(); 1009 data.screenTitle = res.getString(R.string.wifi_settings); 1010 data.enabled = enabled; 1011 result.add(data); 1012 } 1013 1014 return result; 1015 } 1016 }; 1017} 1018