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