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 static android.net.ConnectivityManager.TETHERING_BLUETOOTH; 20 import static android.net.ConnectivityManager.TETHERING_USB; 21 import static android.net.TetheringManager.TETHERING_ETHERNET; 22 23 import android.app.Activity; 24 import android.app.settings.SettingsEnums; 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.Intent; 31 import android.content.IntentFilter; 32 import android.hardware.usb.UsbManager; 33 import android.net.ConnectivityManager; 34 import android.net.EthernetManager; 35 import android.net.TetheringManager; 36 import android.net.wifi.WifiManager; 37 import android.os.Bundle; 38 import android.os.Environment; 39 import android.os.Handler; 40 import android.os.HandlerExecutor; 41 import android.os.UserManager; 42 import android.provider.SearchIndexableResource; 43 import android.text.TextUtils; 44 import android.util.FeatureFlagUtils; 45 46 import androidx.annotation.VisibleForTesting; 47 import androidx.preference.Preference; 48 import androidx.preference.SwitchPreference; 49 50 import com.android.settings.core.FeatureFlags; 51 import com.android.settings.datausage.DataSaverBackend; 52 import com.android.settings.search.BaseSearchIndexProvider; 53 import com.android.settings.wifi.tether.WifiTetherPreferenceController; 54 import com.android.settingslib.TetherUtil; 55 import com.android.settingslib.search.SearchIndexable; 56 57 import java.lang.ref.WeakReference; 58 import java.util.ArrayList; 59 import java.util.Arrays; 60 import java.util.List; 61 import java.util.concurrent.atomic.AtomicReference; 62 63 /* 64 * Displays preferences for Tethering. 65 */ 66 @SearchIndexable 67 public class TetherSettings extends RestrictedSettingsFragment 68 implements DataSaverBackend.Listener { 69 70 @VisibleForTesting 71 static final String KEY_TETHER_PREFS_SCREEN = "tether_prefs_screen"; 72 @VisibleForTesting 73 static final String KEY_WIFI_TETHER = "wifi_tether"; 74 @VisibleForTesting 75 static final String KEY_USB_TETHER_SETTINGS = "usb_tether_settings"; 76 @VisibleForTesting 77 static final String KEY_ENABLE_BLUETOOTH_TETHERING = "enable_bluetooth_tethering"; 78 private static final String KEY_ENABLE_ETHERNET_TETHERING = "enable_ethernet_tethering"; 79 private static final String KEY_DATA_SAVER_FOOTER = "disabled_on_data_saver"; 80 @VisibleForTesting 81 static final String KEY_TETHER_PREFS_FOOTER = "tether_prefs_footer"; 82 83 @VisibleForTesting 84 static final String BLUETOOTH_TETHERING_STATE_CHANGED = 85 "android.bluetooth.pan.profile.action.TETHERING_STATE_CHANGED"; 86 87 private static final String TAG = "TetheringSettings"; 88 89 private SwitchPreference mUsbTether; 90 91 private SwitchPreference mBluetoothTether; 92 93 private SwitchPreference mEthernetTether; 94 95 private BroadcastReceiver mTetherChangeReceiver; 96 97 private String[] mUsbRegexs; 98 private String[] mBluetoothRegexs; 99 private String mEthernetRegex; 100 private AtomicReference<BluetoothPan> mBluetoothPan = new AtomicReference<>(); 101 102 private Handler mHandler = new Handler(); 103 private OnStartTetheringCallback mStartTetheringCallback; 104 private ConnectivityManager mCm; 105 private EthernetManager mEm; 106 private TetheringManager mTm; 107 private TetheringEventCallback mTetheringEventCallback; 108 private EthernetListener mEthernetListener; 109 110 private WifiTetherPreferenceController mWifiTetherPreferenceController; 111 112 private boolean mUsbConnected; 113 private boolean mMassStorageActive; 114 115 private boolean mBluetoothEnableForTether; 116 private boolean mUnavailable; 117 118 private DataSaverBackend mDataSaverBackend; 119 private boolean mDataSaverEnabled; 120 private Preference mDataSaverFooter; 121 122 @Override getMetricsCategory()123 public int getMetricsCategory() { 124 return SettingsEnums.TETHER; 125 } 126 TetherSettings()127 public TetherSettings() { 128 super(UserManager.DISALLOW_CONFIG_TETHERING); 129 } 130 131 @Override onAttach(Context context)132 public void onAttach(Context context) { 133 super.onAttach(context); 134 mWifiTetherPreferenceController = 135 new WifiTetherPreferenceController(context, getSettingsLifecycle()); 136 } 137 138 @Override onCreate(Bundle icicle)139 public void onCreate(Bundle icicle) { 140 super.onCreate(icicle); 141 142 addPreferencesFromResource(R.xml.tether_prefs); 143 mDataSaverBackend = new DataSaverBackend(getContext()); 144 mDataSaverEnabled = mDataSaverBackend.isDataSaverEnabled(); 145 mDataSaverFooter = findPreference(KEY_DATA_SAVER_FOOTER); 146 147 setIfOnlyAvailableForAdmins(true); 148 if (isUiRestricted()) { 149 mUnavailable = true; 150 getPreferenceScreen().removeAll(); 151 return; 152 } 153 154 final Activity activity = getActivity(); 155 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 156 if (adapter != null) { 157 adapter.getProfileProxy(activity.getApplicationContext(), mProfileServiceListener, 158 BluetoothProfile.PAN); 159 } 160 161 setupTetherPreference(); 162 setFooterPreferenceTitle(); 163 164 mDataSaverBackend.addListener(this); 165 166 mCm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 167 mEm = (EthernetManager) getSystemService(Context.ETHERNET_SERVICE); 168 mTm = (TetheringManager) getSystemService(Context.TETHERING_SERVICE); 169 170 mUsbRegexs = mCm.getTetherableUsbRegexs(); 171 mBluetoothRegexs = mCm.getTetherableBluetoothRegexs(); 172 mEthernetRegex = getContext().getResources().getString( 173 com.android.internal.R.string.config_ethernet_iface_regex); 174 175 final boolean usbAvailable = mUsbRegexs.length != 0; 176 final boolean bluetoothAvailable = adapter != null && mBluetoothRegexs.length != 0; 177 final boolean ethernetAvailable = !TextUtils.isEmpty(mEthernetRegex); 178 179 if (!usbAvailable || Utils.isMonkeyRunning()) { 180 getPreferenceScreen().removePreference(mUsbTether); 181 } 182 183 mWifiTetherPreferenceController.displayPreference(getPreferenceScreen()); 184 185 if (!bluetoothAvailable) { 186 getPreferenceScreen().removePreference(mBluetoothTether); 187 } else { 188 BluetoothPan pan = mBluetoothPan.get(); 189 if (pan != null && pan.isTetheringOn()) { 190 mBluetoothTether.setChecked(true); 191 } else { 192 mBluetoothTether.setChecked(false); 193 } 194 } 195 if (!ethernetAvailable) getPreferenceScreen().removePreference(mEthernetTether); 196 // Set initial state based on Data Saver mode. 197 onDataSaverChanged(mDataSaverBackend.isDataSaverEnabled()); 198 } 199 200 @Override onDestroy()201 public void onDestroy() { 202 mDataSaverBackend.remListener(this); 203 204 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 205 BluetoothProfile profile = mBluetoothPan.getAndSet(null); 206 if (profile != null && adapter != null) { 207 adapter.closeProfileProxy(BluetoothProfile.PAN, profile); 208 } 209 210 super.onDestroy(); 211 } 212 213 @VisibleForTesting setupTetherPreference()214 void setupTetherPreference() { 215 mUsbTether = (SwitchPreference) findPreference(KEY_USB_TETHER_SETTINGS); 216 mBluetoothTether = (SwitchPreference) findPreference(KEY_ENABLE_BLUETOOTH_TETHERING); 217 mEthernetTether = (SwitchPreference) findPreference(KEY_ENABLE_ETHERNET_TETHERING); 218 } 219 220 @Override onDataSaverChanged(boolean isDataSaving)221 public void onDataSaverChanged(boolean isDataSaving) { 222 mDataSaverEnabled = isDataSaving; 223 mUsbTether.setEnabled(!mDataSaverEnabled); 224 mBluetoothTether.setEnabled(!mDataSaverEnabled); 225 mEthernetTether.setEnabled(!mDataSaverEnabled); 226 mDataSaverFooter.setVisible(mDataSaverEnabled); 227 } 228 229 @Override onWhitelistStatusChanged(int uid, boolean isWhitelisted)230 public void onWhitelistStatusChanged(int uid, boolean isWhitelisted) { 231 } 232 233 @Override onBlacklistStatusChanged(int uid, boolean isBlacklisted)234 public void onBlacklistStatusChanged(int uid, boolean isBlacklisted) { 235 } 236 237 @VisibleForTesting setFooterPreferenceTitle()238 void setFooterPreferenceTitle() { 239 final Preference footerPreference = findPreference(KEY_TETHER_PREFS_FOOTER); 240 final WifiManager wifiManager = 241 (WifiManager) getContext().getSystemService(Context.WIFI_SERVICE); 242 if (wifiManager.isStaApConcurrencySupported()) { 243 footerPreference.setTitle(R.string.tethering_footer_info_sta_ap_concurrency); 244 } else { 245 footerPreference.setTitle(R.string.tethering_footer_info); 246 } 247 } 248 249 private class TetherChangeReceiver extends BroadcastReceiver { 250 @Override onReceive(Context content, Intent intent)251 public void onReceive(Context content, Intent intent) { 252 String action = intent.getAction(); 253 // TODO: stop using ACTION_TETHER_STATE_CHANGED and use mTetheringEventCallback instead. 254 if (action.equals(ConnectivityManager.ACTION_TETHER_STATE_CHANGED)) { 255 // TODO - this should understand the interface types 256 ArrayList<String> available = intent.getStringArrayListExtra( 257 ConnectivityManager.EXTRA_AVAILABLE_TETHER); 258 ArrayList<String> active = intent.getStringArrayListExtra( 259 ConnectivityManager.EXTRA_ACTIVE_TETHER); 260 ArrayList<String> errored = intent.getStringArrayListExtra( 261 ConnectivityManager.EXTRA_ERRORED_TETHER); 262 updateState(available.toArray(new String[available.size()]), 263 active.toArray(new String[active.size()]), 264 errored.toArray(new String[errored.size()])); 265 } else if (action.equals(Intent.ACTION_MEDIA_SHARED)) { 266 mMassStorageActive = true; 267 updateState(); 268 } else if (action.equals(Intent.ACTION_MEDIA_UNSHARED)) { 269 mMassStorageActive = false; 270 updateState(); 271 } else if (action.equals(UsbManager.ACTION_USB_STATE)) { 272 mUsbConnected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false); 273 updateState(); 274 } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 275 if (mBluetoothEnableForTether) { 276 switch (intent 277 .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) { 278 case BluetoothAdapter.STATE_ON: 279 startTethering(TETHERING_BLUETOOTH); 280 mBluetoothEnableForTether = false; 281 break; 282 283 case BluetoothAdapter.STATE_OFF: 284 case BluetoothAdapter.ERROR: 285 mBluetoothEnableForTether = false; 286 break; 287 288 default: 289 // ignore transition states 290 } 291 } 292 updateState(); 293 } else if (action.equals(BLUETOOTH_TETHERING_STATE_CHANGED)) { 294 updateState(); 295 } 296 } 297 } 298 299 @Override onStart()300 public void onStart() { 301 super.onStart(); 302 303 if (mUnavailable) { 304 if (!isUiRestrictedByOnlyAdmin()) { 305 getEmptyTextView().setText(R.string.tethering_settings_not_available); 306 } 307 getPreferenceScreen().removeAll(); 308 return; 309 } 310 311 312 mStartTetheringCallback = new OnStartTetheringCallback(this); 313 mTetheringEventCallback = new TetheringEventCallback(); 314 mTm.registerTetheringEventCallback(new HandlerExecutor(mHandler), mTetheringEventCallback); 315 316 mMassStorageActive = Environment.MEDIA_SHARED.equals(Environment.getExternalStorageState()); 317 registerReceiver(); 318 319 mEthernetListener = new EthernetListener(); 320 if (mEm != null) 321 mEm.addListener(mEthernetListener); 322 323 updateState(); 324 } 325 326 @Override onStop()327 public void onStop() { 328 super.onStop(); 329 330 if (mUnavailable) { 331 return; 332 } 333 getActivity().unregisterReceiver(mTetherChangeReceiver); 334 mTm.unregisterTetheringEventCallback(mTetheringEventCallback); 335 if (mEm != null) 336 mEm.removeListener(mEthernetListener); 337 mTetherChangeReceiver = null; 338 mStartTetheringCallback = null; 339 mTetheringEventCallback = null; 340 mEthernetListener = null; 341 } 342 343 @VisibleForTesting registerReceiver()344 void registerReceiver() { 345 final Activity activity = getActivity(); 346 347 mTetherChangeReceiver = new TetherChangeReceiver(); 348 IntentFilter filter = new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED); 349 final Intent intent = activity.registerReceiver(mTetherChangeReceiver, filter); 350 351 filter = new IntentFilter(); 352 filter.addAction(UsbManager.ACTION_USB_STATE); 353 activity.registerReceiver(mTetherChangeReceiver, filter); 354 355 filter = new IntentFilter(); 356 filter.addAction(Intent.ACTION_MEDIA_SHARED); 357 filter.addAction(Intent.ACTION_MEDIA_UNSHARED); 358 filter.addDataScheme("file"); 359 activity.registerReceiver(mTetherChangeReceiver, filter); 360 361 filter = new IntentFilter(); 362 filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED); 363 filter.addAction(BLUETOOTH_TETHERING_STATE_CHANGED); 364 activity.registerReceiver(mTetherChangeReceiver, filter); 365 366 if (intent != null) mTetherChangeReceiver.onReceive(activity, intent); 367 } 368 updateState()369 private void updateState() { 370 final ConnectivityManager cm = 371 (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 372 final String[] available = cm.getTetherableIfaces(); 373 final String[] tethered = cm.getTetheredIfaces(); 374 final String[] errored = cm.getTetheringErroredIfaces(); 375 updateState(available, tethered, errored); 376 } 377 updateState(String[] available, String[] tethered, String[] errored)378 private void updateState(String[] available, String[] tethered, 379 String[] errored) { 380 updateUsbState(available, tethered, errored); 381 updateBluetoothState(); 382 updateEthernetState(available, tethered); 383 } 384 385 @VisibleForTesting updateUsbState(String[] available, String[] tethered, String[] errored)386 void updateUsbState(String[] available, String[] tethered, 387 String[] errored) { 388 boolean usbAvailable = mUsbConnected && !mMassStorageActive; 389 int usbError = ConnectivityManager.TETHER_ERROR_NO_ERROR; 390 for (String s : available) { 391 for (String regex : mUsbRegexs) { 392 if (s.matches(regex)) { 393 if (usbError == ConnectivityManager.TETHER_ERROR_NO_ERROR) { 394 usbError = mCm.getLastTetherError(s); 395 } 396 } 397 } 398 } 399 boolean usbTethered = false; 400 for (String s : tethered) { 401 for (String regex : mUsbRegexs) { 402 if (s.matches(regex)) usbTethered = true; 403 } 404 } 405 boolean usbErrored = false; 406 for (String s: errored) { 407 for (String regex : mUsbRegexs) { 408 if (s.matches(regex)) usbErrored = true; 409 } 410 } 411 412 if (usbTethered) { 413 mUsbTether.setEnabled(!mDataSaverEnabled); 414 mUsbTether.setChecked(true); 415 } else if (usbAvailable) { 416 mUsbTether.setEnabled(!mDataSaverEnabled); 417 mUsbTether.setChecked(false); 418 } else { 419 mUsbTether.setEnabled(false); 420 mUsbTether.setChecked(false); 421 } 422 } 423 424 @VisibleForTesting getBluetoothState()425 int getBluetoothState() { 426 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 427 if (adapter == null) { 428 return BluetoothAdapter.ERROR; 429 } 430 return adapter.getState(); 431 } 432 433 @VisibleForTesting isBluetoothTetheringOn()434 boolean isBluetoothTetheringOn() { 435 final BluetoothPan bluetoothPan = mBluetoothPan.get(); 436 return bluetoothPan != null && bluetoothPan.isTetheringOn(); 437 } 438 updateBluetoothState()439 private void updateBluetoothState() { 440 final int btState = getBluetoothState(); 441 if (btState == BluetoothAdapter.ERROR) { 442 return; 443 } 444 445 if (btState == BluetoothAdapter.STATE_TURNING_OFF) { 446 mBluetoothTether.setEnabled(false); 447 } else if (btState == BluetoothAdapter.STATE_TURNING_ON) { 448 mBluetoothTether.setEnabled(false); 449 } else { 450 if (btState == BluetoothAdapter.STATE_ON && isBluetoothTetheringOn()) { 451 mBluetoothTether.setChecked(true); 452 mBluetoothTether.setEnabled(!mDataSaverEnabled); 453 } else { 454 mBluetoothTether.setEnabled(!mDataSaverEnabled); 455 mBluetoothTether.setChecked(false); 456 } 457 } 458 } 459 460 @VisibleForTesting updateEthernetState(String[] available, String[] tethered)461 void updateEthernetState(String[] available, String[] tethered) { 462 463 boolean isAvailable = false; 464 boolean isTethered = false; 465 466 for (String s : available) { 467 if (s.matches(mEthernetRegex)) isAvailable = true; 468 } 469 470 for (String s : tethered) { 471 if (s.matches(mEthernetRegex)) isTethered = true; 472 } 473 474 if (isTethered) { 475 mEthernetTether.setEnabled(!mDataSaverEnabled); 476 mEthernetTether.setChecked(true); 477 } else if (isAvailable || (mEm != null && mEm.isAvailable())) { 478 mEthernetTether.setEnabled(!mDataSaverEnabled); 479 mEthernetTether.setChecked(false); 480 } else { 481 mEthernetTether.setEnabled(false); 482 mEthernetTether.setChecked(false); 483 } 484 } 485 startTethering(int choice)486 private void startTethering(int choice) { 487 if (choice == TETHERING_BLUETOOTH) { 488 // Turn on Bluetooth first. 489 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 490 if (adapter.getState() == BluetoothAdapter.STATE_OFF) { 491 mBluetoothEnableForTether = true; 492 adapter.enable(); 493 mBluetoothTether.setEnabled(false); 494 return; 495 } 496 } 497 498 mCm.startTethering(choice, true, mStartTetheringCallback, mHandler); 499 } 500 501 @Override onPreferenceTreeClick(Preference preference)502 public boolean onPreferenceTreeClick(Preference preference) { 503 if (preference == mUsbTether) { 504 if (mUsbTether.isChecked()) { 505 startTethering(TETHERING_USB); 506 } else { 507 mCm.stopTethering(TETHERING_USB); 508 } 509 } else if (preference == mBluetoothTether) { 510 if (mBluetoothTether.isChecked()) { 511 startTethering(TETHERING_BLUETOOTH); 512 } else { 513 mCm.stopTethering(TETHERING_BLUETOOTH); 514 } 515 } else if (preference == mEthernetTether) { 516 if (mEthernetTether.isChecked()) { 517 startTethering(TETHERING_ETHERNET); 518 } else { 519 mCm.stopTethering(TETHERING_ETHERNET); 520 } 521 } 522 523 return super.onPreferenceTreeClick(preference); 524 } 525 526 @Override getHelpResource()527 public int getHelpResource() { 528 return R.string.help_url_tether; 529 } 530 531 private BluetoothProfile.ServiceListener mProfileServiceListener = 532 new BluetoothProfile.ServiceListener() { 533 public void onServiceConnected(int profile, BluetoothProfile proxy) { 534 mBluetoothPan.set((BluetoothPan) proxy); 535 } 536 public void onServiceDisconnected(int profile) { 537 mBluetoothPan.set(null); 538 } 539 }; 540 541 public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 542 new BaseSearchIndexProvider() { 543 @Override 544 public List<SearchIndexableResource> getXmlResourcesToIndex( 545 Context context, boolean enabled) { 546 final SearchIndexableResource sir = new SearchIndexableResource(context); 547 sir.xmlResId = R.xml.tether_prefs; 548 return Arrays.asList(sir); 549 } 550 551 @Override 552 protected boolean isPageSearchEnabled(Context context) { 553 return !FeatureFlagUtils.isEnabled(context, FeatureFlags.TETHER_ALL_IN_ONE); 554 } 555 556 @Override 557 public List<String> getNonIndexableKeys(Context context) { 558 final List<String> keys = super.getNonIndexableKeys(context); 559 final ConnectivityManager cm = 560 context.getSystemService(ConnectivityManager.class); 561 562 if (!TetherUtil.isTetherAvailable(context)) { 563 keys.add(KEY_TETHER_PREFS_SCREEN); 564 keys.add(KEY_WIFI_TETHER); 565 } 566 567 final boolean usbAvailable = 568 cm.getTetherableUsbRegexs().length != 0; 569 if (!usbAvailable || Utils.isMonkeyRunning()) { 570 keys.add(KEY_USB_TETHER_SETTINGS); 571 } 572 573 final boolean bluetoothAvailable = 574 cm.getTetherableBluetoothRegexs().length != 0; 575 if (!bluetoothAvailable) { 576 keys.add(KEY_ENABLE_BLUETOOTH_TETHERING); 577 } 578 579 final boolean ethernetAvailable = !TextUtils.isEmpty( 580 context.getResources().getString( 581 com.android.internal.R.string.config_ethernet_iface_regex)); 582 if (!ethernetAvailable) { 583 keys.add(KEY_ENABLE_ETHERNET_TETHERING); 584 } 585 return keys; 586 } 587 }; 588 589 private static final class OnStartTetheringCallback extends 590 ConnectivityManager.OnStartTetheringCallback { 591 final WeakReference<TetherSettings> mTetherSettings; 592 OnStartTetheringCallback(TetherSettings settings)593 OnStartTetheringCallback(TetherSettings settings) { 594 mTetherSettings = new WeakReference<>(settings); 595 } 596 597 @Override onTetheringStarted()598 public void onTetheringStarted() { 599 update(); 600 } 601 602 @Override onTetheringFailed()603 public void onTetheringFailed() { 604 update(); 605 } 606 update()607 private void update() { 608 TetherSettings settings = mTetherSettings.get(); 609 if (settings != null) { 610 settings.updateState(); 611 } 612 } 613 } 614 615 private final class TetheringEventCallback implements TetheringManager.TetheringEventCallback { 616 @Override onTetheredInterfacesChanged(List<String> interfaces)617 public void onTetheredInterfacesChanged(List<String> interfaces) { 618 updateState(); 619 } 620 } 621 622 private final class EthernetListener implements EthernetManager.Listener { onAvailabilityChanged(String iface, boolean isAvailable)623 public void onAvailabilityChanged(String iface, boolean isAvailable) { 624 mHandler.post(TetherSettings.this::updateState); 625 } 626 } 627 } 628