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