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