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