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