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