WifiSettings.java revision c9a6698fd44492841bf83bead93b62ecba8a967e
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 /** verbose logging flag is set only thru developer debugging options */ 716 public static int mVerboseLogging = 0; 717 /** 718 * Shows the latest access points available with supplimental information like 719 * the strength of network and the security for it. 720 */ 721 private void updateAccessPoints() { 722 // Safeguard from some delayed event handling 723 if (getActivity() == null) return; 724 725 if (isRestrictedAndNotPinProtected()) { 726 addMessagePreference(R.string.wifi_empty_list_user_restricted); 727 return; 728 } 729 final int wifiState = mWifiManager.getWifiState(); 730 731 //check if verbose logging has been turned on or off 732 mVerboseLogging = mWifiManager.getVerboseLoggingLevel(); 733 734 switch (wifiState) { 735 case WifiManager.WIFI_STATE_ENABLED: 736 // AccessPoints are automatically sorted with TreeSet. 737 final Collection<AccessPoint> accessPoints = 738 constructAccessPoints(getActivity(), mWifiManager, mLastInfo, mLastState); 739 getPreferenceScreen().removeAll(); 740 if(accessPoints.size() == 0) { 741 addMessagePreference(R.string.wifi_empty_list_wifi_on); 742 } 743 for (AccessPoint accessPoint : accessPoints) { 744 getPreferenceScreen().addPreference(accessPoint); 745 } 746 break; 747 748 case WifiManager.WIFI_STATE_ENABLING: 749 getPreferenceScreen().removeAll(); 750 break; 751 752 case WifiManager.WIFI_STATE_DISABLING: 753 addMessagePreference(R.string.wifi_stopping); 754 break; 755 756 case WifiManager.WIFI_STATE_DISABLED: 757 setOffMessage(); 758 break; 759 } 760 } 761 762 private void setOffMessage() { 763 if (mEmptyView != null) { 764 mEmptyView.setText(R.string.wifi_empty_list_wifi_off); 765 if (android.provider.Settings.Global.getInt(getActivity().getContentResolver(), 766 android.provider.Settings.Global.WIFI_SCAN_ALWAYS_AVAILABLE, 0) == 1) { 767 mEmptyView.append("\n\n"); 768 int resId; 769 if (android.provider.Settings.Secure.isLocationProviderEnabled( 770 getActivity().getContentResolver(), LocationManager.NETWORK_PROVIDER)) { 771 resId = R.string.wifi_scan_notify_text_location_on; 772 } else { 773 resId = R.string.wifi_scan_notify_text_location_off; 774 } 775 CharSequence charSeq = getText(resId); 776 mEmptyView.append(charSeq); 777 } 778 } 779 getPreferenceScreen().removeAll(); 780 } 781 782 private void addMessagePreference(int messageId) { 783 if (mEmptyView != null) mEmptyView.setText(messageId); 784 getPreferenceScreen().removeAll(); 785 } 786 787 /** Returns sorted list of access points */ 788 private static List<AccessPoint> constructAccessPoints(Context context, 789 WifiManager wifiManager, WifiInfo lastInfo, DetailedState lastState) { 790 ArrayList<AccessPoint> accessPoints = new ArrayList<AccessPoint>(); 791 /** Lookup table to more quickly update AccessPoints by only considering objects with the 792 * correct SSID. Maps SSID -> List of AccessPoints with the given SSID. */ 793 Multimap<String, AccessPoint> apMap = new Multimap<String, AccessPoint>(); 794 795 final List<WifiConfiguration> configs = wifiManager.getConfiguredNetworks(); 796 if (configs != null) { 797 for (WifiConfiguration config : configs) { 798 AccessPoint accessPoint = new AccessPoint(context, config); 799 if (lastInfo != null && lastState != null) { 800 accessPoint.update(lastInfo, lastState); 801 } 802 accessPoints.add(accessPoint); 803 apMap.put(accessPoint.ssid, accessPoint); 804 } 805 } 806 807 final List<ScanResult> results = wifiManager.getScanResults(); 808 if (results != null) { 809 for (ScanResult result : results) { 810 // Ignore hidden and ad-hoc networks. 811 if (result.SSID == null || result.SSID.length() == 0 || 812 result.capabilities.contains("[IBSS]")) { 813 continue; 814 } 815 816 boolean found = false; 817 for (AccessPoint accessPoint : apMap.getAll(result.SSID)) { 818 if (accessPoint.update(result)) 819 found = true; 820 } 821 if (!found) { 822 AccessPoint accessPoint = new AccessPoint(context, result); 823 accessPoints.add(accessPoint); 824 apMap.put(accessPoint.ssid, accessPoint); 825 } 826 } 827 } 828 829 // Pre-sort accessPoints to speed preference insertion 830 Collections.sort(accessPoints); 831 return accessPoints; 832 } 833 834 /** A restricted multimap for use in constructAccessPoints */ 835 private static class Multimap<K,V> { 836 private final HashMap<K,List<V>> store = new HashMap<K,List<V>>(); 837 /** retrieve a non-null list of values with key K */ 838 List<V> getAll(K key) { 839 List<V> values = store.get(key); 840 return values != null ? values : Collections.<V>emptyList(); 841 } 842 843 void put(K key, V val) { 844 List<V> curVals = store.get(key); 845 if (curVals == null) { 846 curVals = new ArrayList<V>(3); 847 store.put(key, curVals); 848 } 849 curVals.add(val); 850 } 851 } 852 853 private void handleEvent(Context context, Intent intent) { 854 String action = intent.getAction(); 855 if (WifiManager.WIFI_STATE_CHANGED_ACTION.equals(action)) { 856 updateWifiState(intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, 857 WifiManager.WIFI_STATE_UNKNOWN)); 858 } else if (WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(action) || 859 WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION.equals(action) || 860 WifiManager.LINK_CONFIGURATION_CHANGED_ACTION.equals(action)) { 861 updateAccessPoints(); 862 } else if (WifiManager.SUPPLICANT_STATE_CHANGED_ACTION.equals(action)) { 863 //Ignore supplicant state changes when network is connected 864 //TODO: we should deprecate SUPPLICANT_STATE_CHANGED_ACTION and 865 //introduce a broadcast that combines the supplicant and network 866 //network state change events so the apps dont have to worry about 867 //ignoring supplicant state change when network is connected 868 //to get more fine grained information. 869 SupplicantState state = (SupplicantState) intent.getParcelableExtra( 870 WifiManager.EXTRA_NEW_STATE); 871 if (!mConnected.get() && SupplicantState.isHandshakeState(state)) { 872 updateConnectionState(WifiInfo.getDetailedStateOf(state)); 873 } else { 874 // During a connect, we may have the supplicant 875 // state change affect the detailed network state. 876 // Make sure a lost connection is updated as well. 877 updateConnectionState(null); 878 } 879 } else if (WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(action)) { 880 NetworkInfo info = (NetworkInfo) intent.getParcelableExtra( 881 WifiManager.EXTRA_NETWORK_INFO); 882 mConnected.set(info.isConnected()); 883 changeNextButtonState(info.isConnected()); 884 updateAccessPoints(); 885 updateConnectionState(info.getDetailedState()); 886 if (mAutoFinishOnConnection && info.isConnected()) { 887 Activity activity = getActivity(); 888 if (activity != null) { 889 activity.setResult(Activity.RESULT_OK); 890 activity.finish(); 891 } 892 return; 893 } 894 } else if (WifiManager.RSSI_CHANGED_ACTION.equals(action)) { 895 updateConnectionState(null); 896 } 897 } 898 899 private void updateConnectionState(DetailedState state) { 900 /* sticky broadcasts can call this when wifi is disabled */ 901 if (!mWifiManager.isWifiEnabled()) { 902 mScanner.pause(); 903 return; 904 } 905 906 if (state == DetailedState.OBTAINING_IPADDR) { 907 mScanner.pause(); 908 } else { 909 mScanner.resume(); 910 } 911 912 mLastInfo = mWifiManager.getConnectionInfo(); 913 if (state != null) { 914 mLastState = state; 915 } 916 917 for (int i = getPreferenceScreen().getPreferenceCount() - 1; i >= 0; --i) { 918 // Maybe there's a WifiConfigPreference 919 Preference preference = getPreferenceScreen().getPreference(i); 920 if (preference instanceof AccessPoint) { 921 final AccessPoint accessPoint = (AccessPoint) preference; 922 accessPoint.update(mLastInfo, mLastState); 923 } 924 } 925 } 926 927 private void updateWifiState(int state) { 928 Activity activity = getActivity(); 929 if (activity != null) { 930 activity.invalidateOptionsMenu(); 931 } 932 933 switch (state) { 934 case WifiManager.WIFI_STATE_ENABLED: 935 mScanner.resume(); 936 return; // not break, to avoid the call to pause() below 937 938 case WifiManager.WIFI_STATE_ENABLING: 939 addMessagePreference(R.string.wifi_starting); 940 break; 941 942 case WifiManager.WIFI_STATE_DISABLED: 943 setOffMessage(); 944 break; 945 } 946 947 mLastInfo = null; 948 mLastState = null; 949 mScanner.pause(); 950 } 951 952 private class Scanner extends Handler { 953 private int mRetry = 0; 954 955 void resume() { 956 if (!hasMessages(0)) { 957 sendEmptyMessage(0); 958 } 959 } 960 961 void forceScan() { 962 removeMessages(0); 963 sendEmptyMessage(0); 964 } 965 966 void pause() { 967 mRetry = 0; 968 removeMessages(0); 969 } 970 971 @Override 972 public void handleMessage(Message message) { 973 if (mWifiManager.startScan()) { 974 mRetry = 0; 975 } else if (++mRetry >= 3) { 976 mRetry = 0; 977 Activity activity = getActivity(); 978 if (activity != null) { 979 Toast.makeText(activity, R.string.wifi_fail_to_scan, Toast.LENGTH_LONG).show(); 980 } 981 return; 982 } 983 sendEmptyMessageDelayed(0, WIFI_RESCAN_INTERVAL_MS); 984 } 985 } 986 987 /** 988 * Renames/replaces "Next" button when appropriate. "Next" button usually exists in 989 * Wifi setup screens, not in usual wifi settings screen. 990 * 991 * @param connected true when the device is connected to a wifi network. 992 */ 993 private void changeNextButtonState(boolean connected) { 994 if (mEnableNextOnConnection && hasNextButton()) { 995 getNextButton().setEnabled(connected); 996 } 997 } 998 999 @Override 1000 public void onClick(DialogInterface dialogInterface, int button) { 1001 if (button == WifiDialog.BUTTON_FORGET && mSelectedAccessPoint != null) { 1002 forget(); 1003 } else if (button == WifiDialog.BUTTON_SUBMIT) { 1004 if (mDialog != null) { 1005 submit(mDialog.getController()); 1006 } 1007 } 1008 } 1009 1010 /* package */ void submit(WifiConfigController configController) { 1011 1012 final WifiConfiguration config = configController.getConfig(); 1013 1014 if (config == null) { 1015 if (mSelectedAccessPoint != null 1016 && mSelectedAccessPoint.networkId != INVALID_NETWORK_ID) { 1017 mWifiManager.connect(mSelectedAccessPoint.networkId, 1018 mConnectListener); 1019 } 1020 } else if (config.networkId != INVALID_NETWORK_ID) { 1021 if (mSelectedAccessPoint != null) { 1022 mWifiManager.save(config, mSaveListener); 1023 } 1024 } else { 1025 if (configController.isEdit()) { 1026 mWifiManager.save(config, mSaveListener); 1027 } else { 1028 mWifiManager.connect(config, mConnectListener); 1029 } 1030 } 1031 1032 if (mWifiManager.isWifiEnabled()) { 1033 mScanner.resume(); 1034 } 1035 updateAccessPoints(); 1036 } 1037 1038 /* package */ void forget() { 1039 if (mSelectedAccessPoint.networkId == INVALID_NETWORK_ID) { 1040 // Should not happen, but a monkey seems to triger it 1041 Log.e(TAG, "Failed to forget invalid network " + mSelectedAccessPoint.getConfig()); 1042 return; 1043 } 1044 1045 mWifiManager.forget(mSelectedAccessPoint.networkId, mForgetListener); 1046 1047 if (mWifiManager.isWifiEnabled()) { 1048 mScanner.resume(); 1049 } 1050 updateAccessPoints(); 1051 1052 // We need to rename/replace "Next" button in wifi setup context. 1053 changeNextButtonState(false); 1054 } 1055 1056 /** 1057 * Refreshes acccess points and ask Wifi module to scan networks again. 1058 */ 1059 /* package */ void refreshAccessPoints() { 1060 if (mWifiManager.isWifiEnabled()) { 1061 mScanner.resume(); 1062 } 1063 1064 getPreferenceScreen().removeAll(); 1065 } 1066 1067 /** 1068 * Called when "add network" button is pressed. 1069 */ 1070 /* package */ void onAddNetworkPressed() { 1071 // No exact access point is selected. 1072 mSelectedAccessPoint = null; 1073 showDialog(null, true); 1074 } 1075 1076 /* package */ int getAccessPointsCount() { 1077 final boolean wifiIsEnabled = mWifiManager.isWifiEnabled(); 1078 if (wifiIsEnabled) { 1079 return getPreferenceScreen().getPreferenceCount(); 1080 } else { 1081 return 0; 1082 } 1083 } 1084 1085 /** 1086 * Requests wifi module to pause wifi scan. May be ignored when the module is disabled. 1087 */ 1088 /* package */ void pauseWifiScan() { 1089 if (mWifiManager.isWifiEnabled()) { 1090 mScanner.pause(); 1091 } 1092 } 1093 1094 /** 1095 * Requests wifi module to resume wifi scan. May be ignored when the module is disabled. 1096 */ 1097 /* package */ void resumeWifiScan() { 1098 if (mWifiManager.isWifiEnabled()) { 1099 mScanner.resume(); 1100 } 1101 } 1102 1103 @Override 1104 protected int getHelpResource() { 1105 if (mSetupWizardMode) { 1106 return 0; 1107 } 1108 return R.string.help_url_wifi; 1109 } 1110 1111 /** 1112 * Used as the outer frame of all setup wizard pages that need to adjust their margins based 1113 * on the total size of the available display. (e.g. side margins set to 10% of total width.) 1114 */ 1115 public static class ProportionalOuterFrame extends RelativeLayout { 1116 public ProportionalOuterFrame(Context context) { 1117 super(context); 1118 } 1119 public ProportionalOuterFrame(Context context, AttributeSet attrs) { 1120 super(context, attrs); 1121 } 1122 public ProportionalOuterFrame(Context context, AttributeSet attrs, int defStyle) { 1123 super(context, attrs, defStyle); 1124 } 1125 1126 /** 1127 * Set our margins and title area height proportionally to the available display size 1128 */ 1129 @Override 1130 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 1131 int parentWidth = MeasureSpec.getSize(widthMeasureSpec); 1132 int parentHeight = MeasureSpec.getSize(heightMeasureSpec); 1133 final Resources res = getContext().getResources(); 1134 float titleHeight = res.getFraction(R.dimen.setup_title_height, 1, 1); 1135 float sideMargin = res.getFraction(R.dimen.setup_border_width, 1, 1); 1136 int bottom = res.getDimensionPixelSize(R.dimen.setup_margin_bottom); 1137 setPaddingRelative( 1138 (int) (parentWidth * sideMargin), 1139 0, 1140 (int) (parentWidth * sideMargin), 1141 bottom); 1142 View title = findViewById(R.id.title_area); 1143 if (title != null) { 1144 title.setMinimumHeight((int) (parentHeight * titleHeight)); 1145 } 1146 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 1147 } 1148 } 1149 1150 public static final SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 1151 new BaseSearchIndexProvider() { 1152 @Override 1153 public List<SearchIndexableRaw> getRawDataToIndex(Context context, boolean enabled) { 1154 final List<SearchIndexableRaw> result = new ArrayList<SearchIndexableRaw>(); 1155 final Resources res = context.getResources(); 1156 1157 // Add fragment title 1158 SearchIndexableRaw data = new SearchIndexableRaw(context); 1159 data.title = res.getString(R.string.wifi_settings); 1160 data.screenTitle = res.getString(R.string.wifi_settings); 1161 result.add(data); 1162 1163 // Add available Wi-Fi access points 1164 WifiManager wifiManager = 1165 (WifiManager) context.getSystemService(Context.WIFI_SERVICE); 1166 final Collection<AccessPoint> accessPoints = 1167 constructAccessPoints(context, wifiManager, null, null); 1168 for (AccessPoint accessPoint : accessPoints) { 1169 // We are indexing only the saved Wi-Fi networks. 1170 if (accessPoint.getConfig() == null) continue; 1171 data = new SearchIndexableRaw(context); 1172 data.title = accessPoint.getTitle().toString(); 1173 data.screenTitle = res.getString(R.string.wifi_settings); 1174 data.enabled = enabled; 1175 result.add(data); 1176 } 1177 1178 return result; 1179 } 1180 }; 1181} 1182