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