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