1 /* 2 * Copyright (C) 2008 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; 18 19 import com.android.settings.wifi.WifiApEnabler; 20 import com.android.settings.wifi.WifiApDialog; 21 22 import android.app.Activity; 23 import android.app.AlertDialog; 24 import android.app.Dialog; 25 import android.bluetooth.BluetoothAdapter; 26 import android.bluetooth.BluetoothPan; 27 import android.bluetooth.BluetoothProfile; 28 import android.content.BroadcastReceiver; 29 import android.content.Context; 30 import android.content.DialogInterface; 31 import android.content.Intent; 32 import android.content.IntentFilter; 33 import android.content.res.AssetManager; 34 import android.hardware.usb.UsbManager; 35 import android.net.ConnectivityManager; 36 import android.net.wifi.WifiConfiguration; 37 import android.net.wifi.WifiManager; 38 import android.os.Bundle; 39 import android.os.Environment; 40 import android.os.SystemProperties; 41 import android.os.UserHandle; 42 import android.os.UserManager; 43 import android.preference.CheckBoxPreference; 44 import android.preference.Preference; 45 import android.preference.PreferenceScreen; 46 import android.text.TextUtils; 47 import android.view.ViewGroup; 48 import android.view.ViewParent; 49 import android.webkit.WebView; 50 51 import java.io.InputStream; 52 import java.util.ArrayList; 53 import java.util.concurrent.atomic.AtomicReference; 54 import java.util.Locale; 55 56 /* 57 * Displays preferences for Tethering. 58 */ 59 public class TetherSettings extends SettingsPreferenceFragment 60 implements DialogInterface.OnClickListener, Preference.OnPreferenceChangeListener { 61 private static final String TAG = "TetherSettings"; 62 63 private static final String USB_TETHER_SETTINGS = "usb_tether_settings"; 64 private static final String ENABLE_WIFI_AP = "enable_wifi_ap"; 65 private static final String ENABLE_BLUETOOTH_TETHERING = "enable_bluetooth_tethering"; 66 67 private static final int DIALOG_AP_SETTINGS = 1; 68 69 private WebView mView; 70 private CheckBoxPreference mUsbTether; 71 72 private WifiApEnabler mWifiApEnabler; 73 private CheckBoxPreference mEnableWifiAp; 74 75 private CheckBoxPreference mBluetoothTether; 76 77 private BroadcastReceiver mTetherChangeReceiver; 78 79 private String[] mUsbRegexs; 80 81 private String[] mWifiRegexs; 82 83 private String[] mBluetoothRegexs; 84 private AtomicReference<BluetoothPan> mBluetoothPan = new AtomicReference<BluetoothPan>(); 85 86 private static final String WIFI_AP_SSID_AND_SECURITY = "wifi_ap_ssid_and_security"; 87 private static final int CONFIG_SUBTEXT = R.string.wifi_tether_configure_subtext; 88 89 private String[] mSecurityType; 90 private Preference mCreateNetwork; 91 92 private WifiApDialog mDialog; 93 private WifiManager mWifiManager; 94 private WifiConfiguration mWifiConfig = null; 95 96 private boolean mUsbConnected; 97 private boolean mMassStorageActive; 98 99 private boolean mBluetoothEnableForTether; 100 101 private static final int INVALID = -1; 102 private static final int WIFI_TETHERING = 0; 103 private static final int USB_TETHERING = 1; 104 private static final int BLUETOOTH_TETHERING = 2; 105 106 /* One of INVALID, WIFI_TETHERING, USB_TETHERING or BLUETOOTH_TETHERING */ 107 private int mTetherChoice = INVALID; 108 109 /* Stores the package name and the class name of the provisioning app */ 110 private String[] mProvisionApp; 111 private static final int PROVISION_REQUEST = 0; 112 113 @Override 114 public void onCreate(Bundle icicle) { 115 super.onCreate(icicle); 116 addPreferencesFromResource(R.xml.tether_prefs); 117 118 final Activity activity = getActivity(); 119 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 120 if (adapter != null) { 121 adapter.getProfileProxy(activity.getApplicationContext(), mProfileServiceListener, 122 BluetoothProfile.PAN); 123 } 124 125 mEnableWifiAp = 126 (CheckBoxPreference) findPreference(ENABLE_WIFI_AP); 127 Preference wifiApSettings = findPreference(WIFI_AP_SSID_AND_SECURITY); 128 mUsbTether = (CheckBoxPreference) findPreference(USB_TETHER_SETTINGS); 129 mBluetoothTether = (CheckBoxPreference) findPreference(ENABLE_BLUETOOTH_TETHERING); 130 131 ConnectivityManager cm = 132 (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); 133 134 mUsbRegexs = cm.getTetherableUsbRegexs(); 135 mWifiRegexs = cm.getTetherableWifiRegexs(); 136 mBluetoothRegexs = cm.getTetherableBluetoothRegexs(); 137 138 final boolean usbAvailable = mUsbRegexs.length != 0; 139 final boolean wifiAvailable = mWifiRegexs.length != 0; 140 final boolean bluetoothAvailable = mBluetoothRegexs.length != 0; 141 142 if (!usbAvailable || Utils.isMonkeyRunning()) { 143 getPreferenceScreen().removePreference(mUsbTether); 144 } 145 146 if (wifiAvailable && !Utils.isMonkeyRunning()) { 147 mWifiApEnabler = new WifiApEnabler(activity, mEnableWifiAp); 148 initWifiTethering(); 149 } else { 150 getPreferenceScreen().removePreference(mEnableWifiAp); 151 getPreferenceScreen().removePreference(wifiApSettings); 152 } 153 154 if (!bluetoothAvailable) { 155 getPreferenceScreen().removePreference(mBluetoothTether); 156 } else { 157 BluetoothPan pan = mBluetoothPan.get(); 158 if (pan != null && pan.isTetheringOn()) { 159 mBluetoothTether.setChecked(true); 160 } else { 161 mBluetoothTether.setChecked(false); 162 } 163 } 164 165 mProvisionApp = getResources().getStringArray( 166 com.android.internal.R.array.config_mobile_hotspot_provision_app); 167 168 mView = new WebView(activity); 169 } 170 171 private void initWifiTethering() { 172 final Activity activity = getActivity(); 173 mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE); 174 mWifiConfig = mWifiManager.getWifiApConfiguration(); 175 mSecurityType = getResources().getStringArray(R.array.wifi_ap_security); 176 177 mCreateNetwork = findPreference(WIFI_AP_SSID_AND_SECURITY); 178 179 if (mWifiConfig == null) { 180 final String s = activity.getString( 181 com.android.internal.R.string.wifi_tether_configure_ssid_default); 182 mCreateNetwork.setSummary(String.format(activity.getString(CONFIG_SUBTEXT), 183 s, mSecurityType[WifiApDialog.OPEN_INDEX])); 184 } else { 185 int index = WifiApDialog.getSecurityTypeIndex(mWifiConfig); 186 mCreateNetwork.setSummary(String.format(activity.getString(CONFIG_SUBTEXT), 187 mWifiConfig.SSID, 188 mSecurityType[index])); 189 } 190 } 191 192 private BluetoothProfile.ServiceListener mProfileServiceListener = 193 new BluetoothProfile.ServiceListener() { 194 public void onServiceConnected(int profile, BluetoothProfile proxy) { 195 mBluetoothPan.set((BluetoothPan) proxy); 196 } 197 public void onServiceDisconnected(int profile) { 198 mBluetoothPan.set(null); 199 } 200 }; 201 202 @Override 203 public Dialog onCreateDialog(int id) { 204 if (id == DIALOG_AP_SETTINGS) { 205 final Activity activity = getActivity(); 206 mDialog = new WifiApDialog(activity, this, mWifiConfig); 207 return mDialog; 208 } 209 210 return null; 211 } 212 213 private class TetherChangeReceiver extends BroadcastReceiver { 214 @Override 215 public void onReceive(Context content, Intent intent) { 216 String action = intent.getAction(); 217 if (action.equals(ConnectivityManager.ACTION_TETHER_STATE_CHANGED)) { 218 // TODO - this should understand the interface types 219 ArrayList<String> available = intent.getStringArrayListExtra( 220 ConnectivityManager.EXTRA_AVAILABLE_TETHER); 221 ArrayList<String> active = intent.getStringArrayListExtra( 222 ConnectivityManager.EXTRA_ACTIVE_TETHER); 223 ArrayList<String> errored = intent.getStringArrayListExtra( 224 ConnectivityManager.EXTRA_ERRORED_TETHER); 225 updateState(available.toArray(new String[available.size()]), 226 active.toArray(new String[active.size()]), 227 errored.toArray(new String[errored.size()])); 228 } else if (action.equals(Intent.ACTION_MEDIA_SHARED)) { 229 mMassStorageActive = true; 230 updateState(); 231 } else if (action.equals(Intent.ACTION_MEDIA_UNSHARED)) { 232 mMassStorageActive = false; 233 updateState(); 234 } else if (action.equals(UsbManager.ACTION_USB_STATE)) { 235 mUsbConnected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false); 236 updateState(); 237 } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 238 if (mBluetoothEnableForTether) { 239 switch (intent 240 .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) { 241 case BluetoothAdapter.STATE_ON: 242 BluetoothPan bluetoothPan = mBluetoothPan.get(); 243 if (bluetoothPan != null) { 244 bluetoothPan.setBluetoothTethering(true); 245 mBluetoothEnableForTether = false; 246 } 247 break; 248 249 case BluetoothAdapter.STATE_OFF: 250 case BluetoothAdapter.ERROR: 251 mBluetoothEnableForTether = false; 252 break; 253 254 default: 255 // ignore transition states 256 } 257 } 258 updateState(); 259 } 260 } 261 } 262 263 @Override 264 public void onStart() { 265 super.onStart(); 266 267 final Activity activity = getActivity(); 268 269 mMassStorageActive = Environment.MEDIA_SHARED.equals(Environment.getExternalStorageState()); 270 mTetherChangeReceiver = new TetherChangeReceiver(); 271 IntentFilter filter = new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED); 272 Intent intent = activity.registerReceiver(mTetherChangeReceiver, filter); 273 274 filter = new IntentFilter(); 275 filter.addAction(UsbManager.ACTION_USB_STATE); 276 activity.registerReceiver(mTetherChangeReceiver, filter); 277 278 filter = new IntentFilter(); 279 filter.addAction(Intent.ACTION_MEDIA_SHARED); 280 filter.addAction(Intent.ACTION_MEDIA_UNSHARED); 281 filter.addDataScheme("file"); 282 activity.registerReceiver(mTetherChangeReceiver, filter); 283 284 filter = new IntentFilter(); 285 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 286 activity.registerReceiver(mTetherChangeReceiver, filter); 287 288 if (intent != null) mTetherChangeReceiver.onReceive(activity, intent); 289 if (mWifiApEnabler != null) { 290 mEnableWifiAp.setOnPreferenceChangeListener(this); 291 mWifiApEnabler.resume(); 292 } 293 294 updateState(); 295 } 296 297 @Override 298 public void onStop() { 299 super.onStop(); 300 getActivity().unregisterReceiver(mTetherChangeReceiver); 301 mTetherChangeReceiver = null; 302 if (mWifiApEnabler != null) { 303 mEnableWifiAp.setOnPreferenceChangeListener(null); 304 mWifiApEnabler.pause(); 305 } 306 } 307 308 private void updateState() { 309 ConnectivityManager cm = 310 (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); 311 312 String[] available = cm.getTetherableIfaces(); 313 String[] tethered = cm.getTetheredIfaces(); 314 String[] errored = cm.getTetheringErroredIfaces(); 315 updateState(available, tethered, errored); 316 } 317 318 private void updateState(String[] available, String[] tethered, 319 String[] errored) { 320 updateUsbState(available, tethered, errored); 321 updateBluetoothState(available, tethered, errored); 322 } 323 324 325 private void updateUsbState(String[] available, String[] tethered, 326 String[] errored) { 327 ConnectivityManager cm = 328 (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); 329 boolean usbAvailable = mUsbConnected && !mMassStorageActive; 330 int usbError = ConnectivityManager.TETHER_ERROR_NO_ERROR; 331 for (String s : available) { 332 for (String regex : mUsbRegexs) { 333 if (s.matches(regex)) { 334 if (usbError == ConnectivityManager.TETHER_ERROR_NO_ERROR) { 335 usbError = cm.getLastTetherError(s); 336 } 337 } 338 } 339 } 340 boolean usbTethered = false; 341 for (String s : tethered) { 342 for (String regex : mUsbRegexs) { 343 if (s.matches(regex)) usbTethered = true; 344 } 345 } 346 boolean usbErrored = false; 347 for (String s: errored) { 348 for (String regex : mUsbRegexs) { 349 if (s.matches(regex)) usbErrored = true; 350 } 351 } 352 353 if (usbTethered) { 354 mUsbTether.setSummary(R.string.usb_tethering_active_subtext); 355 mUsbTether.setEnabled(true); 356 mUsbTether.setChecked(true); 357 } else if (usbAvailable) { 358 if (usbError == ConnectivityManager.TETHER_ERROR_NO_ERROR) { 359 mUsbTether.setSummary(R.string.usb_tethering_available_subtext); 360 } else { 361 mUsbTether.setSummary(R.string.usb_tethering_errored_subtext); 362 } 363 mUsbTether.setEnabled(true); 364 mUsbTether.setChecked(false); 365 } else if (usbErrored) { 366 mUsbTether.setSummary(R.string.usb_tethering_errored_subtext); 367 mUsbTether.setEnabled(false); 368 mUsbTether.setChecked(false); 369 } else if (mMassStorageActive) { 370 mUsbTether.setSummary(R.string.usb_tethering_storage_active_subtext); 371 mUsbTether.setEnabled(false); 372 mUsbTether.setChecked(false); 373 } else { 374 mUsbTether.setSummary(R.string.usb_tethering_unavailable_subtext); 375 mUsbTether.setEnabled(false); 376 mUsbTether.setChecked(false); 377 } 378 } 379 380 private void updateBluetoothState(String[] available, String[] tethered, 381 String[] errored) { 382 boolean bluetoothErrored = false; 383 for (String s: errored) { 384 for (String regex : mBluetoothRegexs) { 385 if (s.matches(regex)) bluetoothErrored = true; 386 } 387 } 388 389 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 390 int btState = adapter.getState(); 391 if (btState == BluetoothAdapter.STATE_TURNING_OFF) { 392 mBluetoothTether.setEnabled(false); 393 mBluetoothTether.setSummary(R.string.bluetooth_turning_off); 394 } else if (btState == BluetoothAdapter.STATE_TURNING_ON) { 395 mBluetoothTether.setEnabled(false); 396 mBluetoothTether.setSummary(R.string.bluetooth_turning_on); 397 } else { 398 BluetoothPan bluetoothPan = mBluetoothPan.get(); 399 if (btState == BluetoothAdapter.STATE_ON && bluetoothPan != null && 400 bluetoothPan.isTetheringOn()) { 401 mBluetoothTether.setChecked(true); 402 mBluetoothTether.setEnabled(true); 403 int bluetoothTethered = bluetoothPan.getConnectedDevices().size(); 404 if (bluetoothTethered > 1) { 405 String summary = getString( 406 R.string.bluetooth_tethering_devices_connected_subtext, 407 bluetoothTethered); 408 mBluetoothTether.setSummary(summary); 409 } else if (bluetoothTethered == 1) { 410 mBluetoothTether.setSummary( 411 R.string.bluetooth_tethering_device_connected_subtext); 412 } else if (bluetoothErrored) { 413 mBluetoothTether.setSummary(R.string.bluetooth_tethering_errored_subtext); 414 } else { 415 mBluetoothTether.setSummary(R.string.bluetooth_tethering_available_subtext); 416 } 417 } else { 418 mBluetoothTether.setEnabled(true); 419 mBluetoothTether.setChecked(false); 420 mBluetoothTether.setSummary(R.string.bluetooth_tethering_off_subtext); 421 } 422 } 423 } 424 425 public boolean onPreferenceChange(Preference preference, Object value) { 426 boolean enable = (Boolean) value; 427 428 if (enable) { 429 startProvisioningIfNecessary(WIFI_TETHERING); 430 } else { 431 mWifiApEnabler.setSoftapEnabled(false); 432 } 433 return false; 434 } 435 436 boolean isProvisioningNeeded() { 437 if (SystemProperties.getBoolean("net.tethering.noprovisioning", false)) { 438 return false; 439 } 440 return mProvisionApp.length == 2; 441 } 442 443 private void startProvisioningIfNecessary(int choice) { 444 mTetherChoice = choice; 445 if (isProvisioningNeeded()) { 446 Intent intent = new Intent(Intent.ACTION_MAIN); 447 intent.setClassName(mProvisionApp[0], mProvisionApp[1]); 448 startActivityForResult(intent, PROVISION_REQUEST); 449 } else { 450 startTethering(); 451 } 452 } 453 454 public void onActivityResult(int requestCode, int resultCode, Intent intent) { 455 super.onActivityResult(requestCode, resultCode, intent); 456 if (requestCode == PROVISION_REQUEST) { 457 if (resultCode == Activity.RESULT_OK) { 458 startTethering(); 459 } else { 460 //BT and USB need checkbox turned off on failure 461 //Wifi tethering is never turned on until afterwards 462 switch (mTetherChoice) { 463 case BLUETOOTH_TETHERING: 464 mBluetoothTether.setChecked(false); 465 break; 466 case USB_TETHERING: 467 mUsbTether.setChecked(false); 468 break; 469 } 470 mTetherChoice = INVALID; 471 } 472 } 473 } 474 475 private void startTethering() { 476 switch (mTetherChoice) { 477 case WIFI_TETHERING: 478 mWifiApEnabler.setSoftapEnabled(true); 479 break; 480 case BLUETOOTH_TETHERING: 481 // turn on Bluetooth first 482 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 483 if (adapter.getState() == BluetoothAdapter.STATE_OFF) { 484 mBluetoothEnableForTether = true; 485 adapter.enable(); 486 mBluetoothTether.setSummary(R.string.bluetooth_turning_on); 487 mBluetoothTether.setEnabled(false); 488 } else { 489 BluetoothPan bluetoothPan = mBluetoothPan.get(); 490 if (bluetoothPan != null) bluetoothPan.setBluetoothTethering(true); 491 mBluetoothTether.setSummary(R.string.bluetooth_tethering_available_subtext); 492 } 493 break; 494 case USB_TETHERING: 495 setUsbTethering(true); 496 break; 497 default: 498 //should not happen 499 break; 500 } 501 } 502 503 private void setUsbTethering(boolean enabled) { 504 ConnectivityManager cm = 505 (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); 506 mUsbTether.setChecked(false); 507 if (cm.setUsbTethering(enabled) != ConnectivityManager.TETHER_ERROR_NO_ERROR) { 508 mUsbTether.setSummary(R.string.usb_tethering_errored_subtext); 509 return; 510 } 511 mUsbTether.setSummary(""); 512 } 513 514 @Override 515 public boolean onPreferenceTreeClick(PreferenceScreen screen, Preference preference) { 516 ConnectivityManager cm = 517 (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE); 518 519 if (preference == mUsbTether) { 520 boolean newState = mUsbTether.isChecked(); 521 522 if (newState) { 523 startProvisioningIfNecessary(USB_TETHERING); 524 } else { 525 setUsbTethering(newState); 526 } 527 } else if (preference == mBluetoothTether) { 528 boolean bluetoothTetherState = mBluetoothTether.isChecked(); 529 530 if (bluetoothTetherState) { 531 startProvisioningIfNecessary(BLUETOOTH_TETHERING); 532 } else { 533 boolean errored = false; 534 535 String [] tethered = cm.getTetheredIfaces(); 536 String bluetoothIface = findIface(tethered, mBluetoothRegexs); 537 if (bluetoothIface != null && 538 cm.untether(bluetoothIface) != ConnectivityManager.TETHER_ERROR_NO_ERROR) { 539 errored = true; 540 } 541 542 BluetoothPan bluetoothPan = mBluetoothPan.get(); 543 if (bluetoothPan != null) bluetoothPan.setBluetoothTethering(false); 544 if (errored) { 545 mBluetoothTether.setSummary(R.string.bluetooth_tethering_errored_subtext); 546 } else { 547 mBluetoothTether.setSummary(R.string.bluetooth_tethering_off_subtext); 548 } 549 } 550 } else if (preference == mCreateNetwork) { 551 showDialog(DIALOG_AP_SETTINGS); 552 } 553 554 return super.onPreferenceTreeClick(screen, preference); 555 } 556 557 private static String findIface(String[] ifaces, String[] regexes) { 558 for (String iface : ifaces) { 559 for (String regex : regexes) { 560 if (iface.matches(regex)) { 561 return iface; 562 } 563 } 564 } 565 return null; 566 } 567 568 public void onClick(DialogInterface dialogInterface, int button) { 569 if (button == DialogInterface.BUTTON_POSITIVE) { 570 mWifiConfig = mDialog.getConfig(); 571 if (mWifiConfig != null) { 572 /** 573 * if soft AP is stopped, bring up 574 * else restart with new config 575 * TODO: update config on a running access point when framework support is added 576 */ 577 if (mWifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED) { 578 mWifiManager.setWifiApEnabled(null, false); 579 mWifiManager.setWifiApEnabled(mWifiConfig, true); 580 } else { 581 mWifiManager.setWifiApConfiguration(mWifiConfig); 582 } 583 int index = WifiApDialog.getSecurityTypeIndex(mWifiConfig); 584 mCreateNetwork.setSummary(String.format(getActivity().getString(CONFIG_SUBTEXT), 585 mWifiConfig.SSID, 586 mSecurityType[index])); 587 } 588 } 589 } 590 591 @Override 592 public int getHelpResource() { 593 return R.string.help_url_tether; 594 } 595 596 /** 597 * Checks whether this screen will have anything to show on this device. This is called by 598 * the shortcut picker for Settings shortcuts (home screen widget). 599 * @param context a context object for getting a system service. 600 * @return whether Tether & portable hotspot should be shown in the shortcuts picker. 601 */ 602 public static boolean showInShortcuts(Context context) { 603 final ConnectivityManager cm = 604 (ConnectivityManager)context.getSystemService(Context.CONNECTIVITY_SERVICE); 605 final boolean isSecondaryUser = UserHandle.myUserId() != UserHandle.USER_OWNER; 606 return !isSecondaryUser && cm.isTetheringSupported(); 607 } 608 } 609