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