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