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