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