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