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