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