WifiSettings.java revision 88c9f2889fafb53a2875418317ecd197d9aae883
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.security.KeyStore; 50import android.telephony.TelephonyManager; 51import android.util.AttributeSet; 52import android.util.Log; 53import android.view.ContextMenu; 54import android.view.ContextMenu.ContextMenuInfo; 55import android.view.Gravity; 56import android.view.LayoutInflater; 57import android.view.Menu; 58import android.view.MenuInflater; 59import android.view.MenuItem; 60import android.view.View; 61import android.view.View.OnClickListener; 62import android.view.ViewGroup; 63import android.widget.AdapterView.AdapterContextMenuInfo; 64import android.widget.Button; 65import android.widget.ImageButton; 66import android.widget.PopupMenu; 67import android.widget.PopupMenu.OnMenuItemClickListener; 68import android.widget.RelativeLayout; 69import android.widget.Switch; 70import android.widget.TextView; 71import android.widget.Toast; 72 73import com.android.settings.R; 74import com.android.settings.SettingsPreferenceFragment; 75import com.android.settings.wifi.p2p.WifiP2pSettings; 76 77import java.util.ArrayList; 78import java.util.Collection; 79import java.util.Collections; 80import java.util.HashMap; 81import java.util.List; 82import java.util.concurrent.atomic.AtomicBoolean; 83 84/** 85 * Two types of UI are provided here. 86 * 87 * The first is for "usual Settings", appearing as any other Setup fragment. 88 * 89 * The second is for Setup Wizard, with a simplified interface that hides the action bar 90 * and menus. 91 */ 92public class WifiSettings extends SettingsPreferenceFragment 93 implements DialogInterface.OnClickListener { 94 private static final String TAG = "WifiSettings"; 95 private static final int MENU_ID_WPS_PBC = Menu.FIRST; 96 private static final int MENU_ID_WPS_PIN = Menu.FIRST + 1; 97 private static final int MENU_ID_P2P = Menu.FIRST + 2; 98 private static final int MENU_ID_ADD_NETWORK = Menu.FIRST + 3; 99 private static final int MENU_ID_ADVANCED = Menu.FIRST + 4; 100 private static final int MENU_ID_SCAN = Menu.FIRST + 5; 101 private static final int MENU_ID_CONNECT = Menu.FIRST + 6; 102 private static final int MENU_ID_FORGET = Menu.FIRST + 7; 103 private static final int MENU_ID_MODIFY = Menu.FIRST + 8; 104 105 private static final int WIFI_DIALOG_ID = 1; 106 private static final int WPS_PBC_DIALOG_ID = 2; 107 private static final int WPS_PIN_DIALOG_ID = 3; 108 private static final int WIFI_SKIPPED_DIALOG_ID = 4; 109 private static final int WIFI_AND_MOBILE_SKIPPED_DIALOG_ID = 5; 110 111 // Combo scans can take 5-6s to complete - set to 10s. 112 private static final int WIFI_RESCAN_INTERVAL_MS = 10 * 1000; 113 114 // Instance state keys 115 private static final String SAVE_DIALOG_EDIT_MODE = "edit_mode"; 116 private static final String SAVE_DIALOG_ACCESS_POINT_STATE = "wifi_ap_state"; 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 int mKeyStoreNetworkId = INVALID_NETWORK_ID; 139 140 private WifiDialog mDialog; 141 142 private TextView mEmptyView; 143 144 /* Used in Wifi Setup context */ 145 146 // this boolean extra specifies whether to disable the Next button when not connected 147 private static final String EXTRA_ENABLE_NEXT_ON_CONNECT = "wifi_enable_next_on_connect"; 148 149 // this boolean extra specifies whether to auto finish when connection is established 150 private static final String EXTRA_AUTO_FINISH_ON_CONNECT = "wifi_auto_finish_on_connect"; 151 152 // this boolean extra shows a custom button that we can control 153 protected static final String EXTRA_SHOW_CUSTOM_BUTTON = "wifi_show_custom_button"; 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 if (getActivity().getIntent().getBooleanExtra(EXTRA_SHOW_CUSTOM_BUTTON, false)) { 242 view.findViewById(R.id.button_bar).setVisibility(View.VISIBLE); 243 view.findViewById(R.id.back_button).setVisibility(View.INVISIBLE); 244 view.findViewById(R.id.skip_button).setVisibility(View.INVISIBLE); 245 view.findViewById(R.id.next_button).setVisibility(View.INVISIBLE); 246 247 Button customButton = (Button) view.findViewById(R.id.custom_button); 248 customButton.setVisibility(View.VISIBLE); 249 customButton.setOnClickListener(new OnClickListener() { 250 @Override 251 public void onClick(View v) { 252 if (isPhone() && !hasSimProblem()) { 253 showDialog(WIFI_SKIPPED_DIALOG_ID); 254 } else { 255 showDialog(WIFI_AND_MOBILE_SKIPPED_DIALOG_ID); 256 } 257 } 258 }); 259 } 260 261 return view; 262 } else { 263 return super.onCreateView(inflater, container, savedInstanceState); 264 } 265 } 266 267 @Override 268 public void onActivityCreated(Bundle savedInstanceState) { 269 super.onActivityCreated(savedInstanceState); 270 271 mP2pSupported = getPackageManager().hasSystemFeature(PackageManager.FEATURE_WIFI_DIRECT); 272 mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); 273 274 mConnectListener = new WifiManager.ActionListener() { 275 public void onSuccess() { 276 } 277 public void onFailure(int reason) { 278 Activity activity = getActivity(); 279 if (activity != null) { 280 Toast.makeText(activity, 281 R.string.wifi_failed_connect_message, 282 Toast.LENGTH_SHORT).show(); 283 } 284 } 285 }; 286 287 mSaveListener = new WifiManager.ActionListener() { 288 public void onSuccess() { 289 } 290 public void onFailure(int reason) { 291 Activity activity = getActivity(); 292 if (activity != null) { 293 Toast.makeText(activity, 294 R.string.wifi_failed_save_message, 295 Toast.LENGTH_SHORT).show(); 296 } 297 } 298 }; 299 300 mForgetListener = new WifiManager.ActionListener() { 301 public void onSuccess() { 302 } 303 public void onFailure(int reason) { 304 Activity activity = getActivity(); 305 if (activity != null) { 306 Toast.makeText(activity, 307 R.string.wifi_failed_forget_message, 308 Toast.LENGTH_SHORT).show(); 309 } 310 } 311 }; 312 313 if (savedInstanceState != null 314 && savedInstanceState.containsKey(SAVE_DIALOG_ACCESS_POINT_STATE)) { 315 mDlgEdit = savedInstanceState.getBoolean(SAVE_DIALOG_EDIT_MODE); 316 mAccessPointSavedState = savedInstanceState.getBundle(SAVE_DIALOG_ACCESS_POINT_STATE); 317 } 318 319 final Activity activity = getActivity(); 320 final Intent intent = activity.getIntent(); 321 322 // first if we're supposed to finish once we have a connection 323 mAutoFinishOnConnection = intent.getBooleanExtra(EXTRA_AUTO_FINISH_ON_CONNECT, false); 324 325 if (mAutoFinishOnConnection) { 326 // Hide the next button 327 if (hasNextButton()) { 328 getNextButton().setVisibility(View.GONE); 329 } 330 331 final ConnectivityManager connectivity = (ConnectivityManager) 332 activity.getSystemService(Context.CONNECTIVITY_SERVICE); 333 if (connectivity != null 334 && connectivity.getNetworkInfo(ConnectivityManager.TYPE_WIFI).isConnected()) { 335 activity.finish(); 336 return; 337 } 338 } 339 340 // if we're supposed to enable/disable the Next button based on our current connection 341 // state, start it off in the right state 342 mEnableNextOnConnection = intent.getBooleanExtra(EXTRA_ENABLE_NEXT_ON_CONNECT, false); 343 344 if (mEnableNextOnConnection) { 345 if (hasNextButton()) { 346 final ConnectivityManager connectivity = (ConnectivityManager) 347 activity.getSystemService(Context.CONNECTIVITY_SERVICE); 348 if (connectivity != null) { 349 NetworkInfo info = connectivity.getNetworkInfo( 350 ConnectivityManager.TYPE_WIFI); 351 changeNextButtonState(info.isConnected()); 352 } 353 } 354 } 355 356 addPreferencesFromResource(R.xml.wifi_settings); 357 358 if (mSetupWizardMode) { 359 getView().setSystemUiVisibility( 360 View.STATUS_BAR_DISABLE_BACK | 361 View.STATUS_BAR_DISABLE_HOME | 362 View.STATUS_BAR_DISABLE_RECENT | 363 View.STATUS_BAR_DISABLE_NOTIFICATION_ALERTS | 364 View.STATUS_BAR_DISABLE_CLOCK); 365 } 366 367 // On/off switch is hidden for Setup Wizard 368 if (!mSetupWizardMode) { 369 Switch actionBarSwitch = new Switch(activity); 370 371 if (activity instanceof PreferenceActivity) { 372 PreferenceActivity preferenceActivity = (PreferenceActivity) activity; 373 if (preferenceActivity.onIsHidingHeaders() || !preferenceActivity.onIsMultiPane()) { 374 final int padding = activity.getResources().getDimensionPixelSize( 375 R.dimen.action_bar_switch_padding); 376 actionBarSwitch.setPadding(0, 0, padding, 0); 377 activity.getActionBar().setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM, 378 ActionBar.DISPLAY_SHOW_CUSTOM); 379 activity.getActionBar().setCustomView(actionBarSwitch, new ActionBar.LayoutParams( 380 ActionBar.LayoutParams.WRAP_CONTENT, 381 ActionBar.LayoutParams.WRAP_CONTENT, 382 Gravity.CENTER_VERTICAL | Gravity.END)); 383 } 384 } 385 386 mWifiEnabler = new WifiEnabler(activity, actionBarSwitch); 387 } 388 389 mEmptyView = (TextView) getView().findViewById(android.R.id.empty); 390 getListView().setEmptyView(mEmptyView); 391 392 if (!mSetupWizardMode) { 393 registerForContextMenu(getListView()); 394 } 395 setHasOptionsMenu(true); 396 } 397 398 @Override 399 public void onResume() { 400 super.onResume(); 401 if (mWifiEnabler != null) { 402 mWifiEnabler.resume(); 403 } 404 405 getActivity().registerReceiver(mReceiver, mFilter); 406 if (mKeyStoreNetworkId != INVALID_NETWORK_ID && 407 KeyStore.getInstance().state() == KeyStore.State.UNLOCKED) { 408 mWifiManager.connect(mKeyStoreNetworkId, mConnectListener); 409 } 410 mKeyStoreNetworkId = INVALID_NETWORK_ID; 411 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 if (!requireKeyStore(mSelectedAccessPoint.getConfig())) { 555 mWifiManager.connect(mSelectedAccessPoint.networkId, 556 mConnectListener); 557 } 558 } else if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE) { 559 /** Bypass dialog for unsecured networks */ 560 mSelectedAccessPoint.generateOpenNetworkConfig(); 561 mWifiManager.connect(mSelectedAccessPoint.getConfig(), 562 mConnectListener); 563 } else { 564 showDialog(mSelectedAccessPoint, true); 565 } 566 return true; 567 } 568 case MENU_ID_FORGET: { 569 mWifiManager.forget(mSelectedAccessPoint.networkId, mForgetListener); 570 return true; 571 } 572 case MENU_ID_MODIFY: { 573 showDialog(mSelectedAccessPoint, true); 574 return true; 575 } 576 } 577 return super.onContextItemSelected(item); 578 } 579 580 @Override 581 public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { 582 if (preference instanceof AccessPoint) { 583 mSelectedAccessPoint = (AccessPoint) preference; 584 /** Bypass dialog for unsecured, unsaved networks */ 585 if (mSelectedAccessPoint.security == AccessPoint.SECURITY_NONE && 586 mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) { 587 mSelectedAccessPoint.generateOpenNetworkConfig(); 588 mWifiManager.connect(mSelectedAccessPoint.getConfig(), mConnectListener); 589 } else { 590 showDialog(mSelectedAccessPoint, false); 591 } 592 } else { 593 return super.onPreferenceTreeClick(screen, preference); 594 } 595 return true; 596 } 597 598 private void showDialog(AccessPoint accessPoint, boolean edit) { 599 if (mDialog != null) { 600 removeDialog(WIFI_DIALOG_ID); 601 mDialog = null; 602 } 603 604 // Save the access point and edit mode 605 mDlgAccessPoint = accessPoint; 606 mDlgEdit = edit; 607 608 showDialog(WIFI_DIALOG_ID); 609 } 610 611 @Override 612 public Dialog onCreateDialog(int dialogId) { 613 switch (dialogId) { 614 case WIFI_DIALOG_ID: 615 AccessPoint ap = mDlgAccessPoint; // For manual launch 616 if (ap == null) { // For re-launch from saved state 617 if (mAccessPointSavedState != null) { 618 ap = new AccessPoint(getActivity(), mAccessPointSavedState); 619 // For repeated orientation changes 620 mDlgAccessPoint = ap; 621 } 622 } 623 // If it's still null, fine, it's for Add Network 624 mSelectedAccessPoint = ap; 625 mDialog = new WifiDialog(getActivity(), this, ap, mDlgEdit); 626 return mDialog; 627 case WPS_PBC_DIALOG_ID: 628 return new WpsDialog(getActivity(), WpsInfo.PBC); 629 case WPS_PIN_DIALOG_ID: 630 return new WpsDialog(getActivity(), WpsInfo.DISPLAY); 631 case WIFI_SKIPPED_DIALOG_ID: 632 return new AlertDialog.Builder(getActivity()) 633 .setMessage(R.string.wifi_skipped_message) 634 .setCancelable(false) 635 .setNegativeButton(R.string.wifi_skip_anyway, 636 new DialogInterface.OnClickListener() { 637 @Override 638 public void onClick(DialogInterface dialog, int id) { 639 getActivity().setResult(Activity.RESULT_CANCELED); 640 getActivity().finish(); 641 } 642 }) 643 .setPositiveButton(R.string.wifi_dont_skip, 644 new DialogInterface.OnClickListener() { 645 @Override 646 public void onClick(DialogInterface dialog, int id) { 647 } 648 }) 649 .create(); 650 case WIFI_AND_MOBILE_SKIPPED_DIALOG_ID: 651 return new AlertDialog.Builder(getActivity()) 652 .setMessage(R.string.wifi_and_mobile_skipped_message) 653 .setCancelable(false) 654 .setNegativeButton(R.string.wifi_skip_anyway, 655 new DialogInterface.OnClickListener() { 656 @Override 657 public void onClick(DialogInterface dialog, int id) { 658 getActivity().setResult(Activity.RESULT_CANCELED); 659 getActivity().finish(); 660 } 661 }) 662 .setPositiveButton(R.string.wifi_dont_skip, 663 new DialogInterface.OnClickListener() { 664 @Override 665 public void onClick(DialogInterface dialog, int id) { 666 } 667 }) 668 .create(); 669 670 } 671 return super.onCreateDialog(dialogId); 672 } 673 674 private boolean isPhone() { 675 final TelephonyManager telephonyManager = (TelephonyManager)this.getSystemService( 676 Context.TELEPHONY_SERVICE); 677 return telephonyManager != null 678 && telephonyManager.getPhoneType() != TelephonyManager.PHONE_TYPE_NONE; 679 } 680 681 /** 682 * Return true if there's any SIM related impediment to connectivity. 683 * Treats Unknown as OK. (Only returns true if we're sure of a SIM problem.) 684 */ 685 protected boolean hasSimProblem() { 686 final TelephonyManager telephonyManager = (TelephonyManager)this.getSystemService( 687 Context.TELEPHONY_SERVICE); 688 return telephonyManager != null 689 && telephonyManager.getCurrentPhoneType() == TelephonyManager.PHONE_TYPE_GSM 690 && telephonyManager.getSimState() != TelephonyManager.SIM_STATE_READY 691 && telephonyManager.getSimState() != TelephonyManager.SIM_STATE_UNKNOWN; 692 } 693 694 private boolean requireKeyStore(WifiConfiguration config) { 695 if (WifiConfigController.requireKeyStore(config) && 696 KeyStore.getInstance().state() != KeyStore.State.UNLOCKED) { 697 mKeyStoreNetworkId = config.networkId; 698 Credentials.getInstance().unlock(getActivity()); 699 return true; 700 } 701 return false; 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 final int wifiState = mWifiManager.getWifiState(); 713 714 switch (wifiState) { 715 case WifiManager.WIFI_STATE_ENABLED: 716 // AccessPoints are automatically sorted with TreeSet. 717 final Collection<AccessPoint> accessPoints = constructAccessPoints(); 718 getPreferenceScreen().removeAll(); 719 if(accessPoints.size() == 0) { 720 addMessagePreference(R.string.wifi_empty_list_wifi_on); 721 } 722 for (AccessPoint accessPoint : accessPoints) { 723 getPreferenceScreen().addPreference(accessPoint); 724 } 725 break; 726 727 case WifiManager.WIFI_STATE_ENABLING: 728 getPreferenceScreen().removeAll(); 729 break; 730 731 case WifiManager.WIFI_STATE_DISABLING: 732 addMessagePreference(R.string.wifi_stopping); 733 break; 734 735 case WifiManager.WIFI_STATE_DISABLED: 736 addMessagePreference(R.string.wifi_empty_list_wifi_off); 737 break; 738 } 739 } 740 741 private void addMessagePreference(int messageId) { 742 if (mEmptyView != null) mEmptyView.setText(messageId); 743 getPreferenceScreen().removeAll(); 744 } 745 746 /** Returns sorted list of access points */ 747 private List<AccessPoint> constructAccessPoints() { 748 ArrayList<AccessPoint> accessPoints = new ArrayList<AccessPoint>(); 749 /** Lookup table to more quickly update AccessPoints by only considering objects with the 750 * correct SSID. Maps SSID -> List of AccessPoints with the given SSID. */ 751 Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>(); 752 753 final List<WifiConfiguration> configs = mWifiManager.getConfiguredNetworks(); 754 if (configs != null) { 755 for (WifiConfiguration config : configs) { 756 AccessPoint accessPoint = new AccessPoint(getActivity(), config); 757 accessPoint.update(mLastInfo, mLastState); 758 accessPoints.add(accessPoint); 759 apMap.put(accessPoint.ssid, accessPoint); 760 } 761 } 762 763 final List<ScanResult> results = mWifiManager.getScanResults(); 764 if (results != null) { 765 for (ScanResult result : results) { 766 // Ignore hidden and ad-hoc networks. 767 if (result.SSID == null || result.SSID.length() == 0 || 768 result.capabilities.contains("[IBSS]")) { 769 continue; 770 } 771 772 boolean found = false; 773 for (AccessPoint accessPoint : apMap.getAll(result.SSID)) { 774 if (accessPoint.update(result)) 775 found = true; 776 } 777 if (!found) { 778 AccessPoint accessPoint = new AccessPoint(getActivity(), result); 779 accessPoints.add(accessPoint); 780 apMap.put(accessPoint.ssid, accessPoint); 781 } 782 } 783 } 784 785 // Pre-sort accessPoints to speed preference insertion 786 Collections.sort(accessPoints); 787 return accessPoints; 788 } 789 790 /** A restricted multimap for use in constructAccessPoints */ 791 private class Multimap<K,V> { 792 private HashMap<K,List<V>> store = new HashMap<K,List<V>>(); 793 /** retrieve a non-null list of values with key K */ 794 List<V> getAll(K key) { 795 List<V> values = store.get(key); 796 return values != null ? values : Collections.<V>emptyList(); 797 } 798 799 void put(K key, V val) { 800 List<V> curVals = store.get(key); 801 if (curVals == null) { 802 curVals = new ArrayList<V>(3); 803 store.put(key, curVals); 804 } 805 curVals.add(val); 806 } 807 } 808 809 private void handleEvent(Context context, Intent intent) { 810 String action = intent.getAction(); 811 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { 812 updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 813 WifiManager.WIFI_STATE_UNKNOWN)); 814 } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) || 815 WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) || 816 WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) { 817 updateAccessPoints(); 818 } else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) { 819 //Ignore supplicant state changes when network is connected 820 //TODO: we should deprecate SUPPLICANT_STATE_CHANGED_ACTION and 821 //introduce a broadcast that combines the supplicant and network 822 //network state change events so the apps dont have to worry about 823 //ignoring supplicant state change when network is connected 824 //to get more fine grained information. 825 SupplicantState state = (SupplicantState) intent.getParcelableExtra( 826 WifiManager.EXTRA_NEW_STATE); 827 if (!mConnected.get() && SupplicantState.isHandshakeState(state)) { 828 updateConnectionState(WifiInfo.getDetailedStateOf(state)); 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.finish(); 841 } 842 return; 843 } 844 } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) { 845 updateConnectionState(null); 846 } 847 } 848 849 private void updateConnectionState(DetailedState state) { 850 /* sticky broadcasts can call this when wifi is disabled */ 851 if (!mWifiManager.isWifiEnabled()) { 852 mScanner.pause(); 853 return; 854 } 855 856 if (state == DetailedState.OBTAINING_IPADDR) { 857 mScanner.pause(); 858 } else { 859 mScanner.resume(); 860 } 861 862 mLastInfo = mWifiManager.getConnectionInfo(); 863 if (state != null) { 864 mLastState = state; 865 } 866 867 for (int i = getPreferenceScreen().getPreferenceCount() - 1; i >= 0; --i) { 868 // Maybe there's a WifiConfigPreference 869 Preference preference = getPreferenceScreen().getPreference(i); 870 if (preference instanceof AccessPoint) { 871 final AccessPoint accessPoint = (AccessPoint) preference; 872 accessPoint.update(mLastInfo, mLastState); 873 } 874 } 875 } 876 877 private void updateWifiState(int state) { 878 Activity activity = getActivity(); 879 if (activity != null) { 880 activity.invalidateOptionsMenu(); 881 } 882 883 switch (state) { 884 case WifiManager.WIFI_STATE_ENABLED: 885 mScanner.resume(); 886 return; // not break, to avoid the call to pause() below 887 888 case WifiManager.WIFI_STATE_ENABLING: 889 addMessagePreference(R.string.wifi_starting); 890 break; 891 892 case WifiManager.WIFI_STATE_DISABLED: 893 addMessagePreference(R.string.wifi_empty_list_wifi_off); 894 break; 895 } 896 897 mLastInfo = null; 898 mLastState = null; 899 mScanner.pause(); 900 } 901 902 private class Scanner extends Handler { 903 private int mRetry = 0; 904 905 void resume() { 906 if (!hasMessages(0)) { 907 sendEmptyMessage(0); 908 } 909 } 910 911 void forceScan() { 912 removeMessages(0); 913 sendEmptyMessage(0); 914 } 915 916 void pause() { 917 mRetry = 0; 918 removeMessages(0); 919 } 920 921 @Override 922 public void handleMessage(Message message) { 923 if (mWifiManager.startScanActive()) { 924 mRetry = 0; 925 } else if (++mRetry >= 3) { 926 mRetry = 0; 927 Activity activity = getActivity(); 928 if (activity != null) { 929 Toast.makeText(activity, R.string.wifi_fail_to_scan, 930 Toast.LENGTH_LONG).show(); 931 } 932 return; 933 } 934 sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS); 935 } 936 } 937 938 /** 939 * Renames/replaces "Next" button when appropriate. "Next" button usually exists in 940 * Wifi setup screens, not in usual wifi settings screen. 941 * 942 * @param connected true when the device is connected to a wifi network. 943 */ 944 private void changeNextButtonState(boolean connected) { 945 if (mEnableNextOnConnection && hasNextButton()) { 946 getNextButton().setEnabled(connected); 947 } 948 } 949 950 public void onClick(DialogInterface dialogInterface, int button) { 951 if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) { 952 forget(); 953 } else if (button == WifiDialog.BUTTON_SUBMIT) { 954 submit(mDialog.getController()); 955 } 956 } 957 958 /* package */ void submit(WifiConfigController configController) { 959 960 final WifiConfiguration config = configController.getConfig(); 961 962 if (config == null) { 963 if (mSelectedAccessPoint != null 964 && !requireKeyStore(mSelectedAccessPoint.getConfig()) 965 && mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) { 966 mWifiManager.connect(mSelectedAccessPoint.networkId, 967 mConnectListener); 968 } 969 } else if (config.networkId != INVALID_NETWORK_ID) { 970 if (mSelectedAccessPoint != null) { 971 mWifiManager.save(config, mSaveListener); 972 } 973 } else { 974 if (configController.isEdit() || requireKeyStore(config)) { 975 mWifiManager.save(config, mSaveListener); 976 } else { 977 mWifiManager.connect(config, mConnectListener); 978 } 979 } 980 981 if (mWifiManager.isWifiEnabled()) { 982 mScanner.resume(); 983 } 984 updateAccessPoints(); 985 } 986 987 /* package */ void forget() { 988 if (mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) { 989 // Should not happen, but a monkey seems to triger it 990 Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig()); 991 return; 992 } 993 994 mWifiManager.forget(mSelectedAccessPoint.networkId, mForgetListener); 995 996 if (mWifiManager.isWifiEnabled()) { 997 mScanner.resume(); 998 } 999 updateAccessPoints(); 1000 1001 // We need to rename/replace "Next" button in wifi setup context. 1002 changeNextButtonState(false); 1003 } 1004 1005 /** 1006 * Refreshes acccess points and ask Wifi module to scan networks again. 1007 */ 1008 /* package */ void refreshAccessPoints() { 1009 if (mWifiManager.isWifiEnabled()) { 1010 mScanner.resume(); 1011 } 1012 1013 getPreferenceScreen().removeAll(); 1014 } 1015 1016 /** 1017 * Called when "add network" button is pressed. 1018 */ 1019 /* package */ void onAddNetworkPressed() { 1020 // No exact access point is selected. 1021 mSelectedAccessPoint = null; 1022 showDialog(null, true); 1023 } 1024 1025 /* package */ int getAccessPointsCount() { 1026 final boolean wifiIsEnabled = mWifiManager.isWifiEnabled(); 1027 if (wifiIsEnabled) { 1028 return getPreferenceScreen().getPreferenceCount(); 1029 } else { 1030 return 0; 1031 } 1032 } 1033 1034 /** 1035 * Requests wifi module to pause wifi scan. May be ignored when the module is disabled. 1036 */ 1037 /* package */ void pauseWifiScan() { 1038 if (mWifiManager.isWifiEnabled()) { 1039 mScanner.pause(); 1040 } 1041 } 1042 1043 /** 1044 * Requests wifi module to resume wifi scan. May be ignored when the module is disabled. 1045 */ 1046 /* package */ void resumeWifiScan() { 1047 if (mWifiManager.isWifiEnabled()) { 1048 mScanner.resume(); 1049 } 1050 } 1051 1052 @Override 1053 protected int getHelpResource() { 1054 if (mSetupWizardMode) { 1055 return 0; 1056 } 1057 return R.string.help_url_wifi; 1058 } 1059 1060 /** 1061 * Used as the outer frame of all setup wizard pages that need to adjust their margins based 1062 * on the total size of the available display. (e.g. side margins set to 10% of total width.) 1063 */ 1064 public static class ProportionalOuterFrame extends RelativeLayout { 1065 public ProportionalOuterFrame(Context context) { 1066 super(context); 1067 } 1068 public ProportionalOuterFrame(Context context, AttributeSet attrs) { 1069 super(context, attrs); 1070 } 1071 public ProportionalOuterFrame(Context context, AttributeSet attrs, int defStyle) { 1072 super(context, attrs, defStyle); 1073 } 1074 1075 /** 1076 * Set our margins and title area height proportionally to the available display size 1077 */ 1078 @Override 1079 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1080 int parentWidth = MeasureSpec.getSize(widthMeasureSpec); 1081 int parentHeight = MeasureSpec.getSize(heightMeasureSpec); 1082 final Resources resources = getContext().getResources(); 1083 float titleHeight = resources.getFraction(R.dimen.setup_title_height, 1, 1); 1084 float sideMargin = resources.getFraction(R.dimen.setup_border_width, 1, 1); 1085 int bottom = resources.getDimensionPixelSize(R.dimen.setup_margin_bottom); 1086 setPadding( 1087 (int) (parentWidth * sideMargin), 1088 0, 1089 (int) (parentWidth * sideMargin), 1090 bottom); 1091 View title = findViewById(R.id.title_area); 1092 if (title != null) { 1093 title.setMinimumHeight((int) (parentHeight * titleHeight)); 1094 } 1095 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 1096 } 1097 } 1098 1099} 1100