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