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