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