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