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 22 import android.app.Activity; 23 import android.app.settings.SettingsEnums; 24 import android.bluetooth.BluetoothAdapter; 25 import android.bluetooth.BluetoothPan; 26 import android.bluetooth.BluetoothProfile; 27 import android.content.BroadcastReceiver; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.IntentFilter; 31 import android.content.pm.PackageManager; 32 import android.hardware.usb.UsbManager; 33 import android.net.ConnectivityManager; 34 import android.os.Bundle; 35 import android.os.Environment; 36 import android.os.Handler; 37 import android.os.UserManager; 38 import android.provider.SearchIndexableResource; 39 40 import androidx.annotation.VisibleForTesting; 41 import androidx.preference.Preference; 42 import androidx.preference.SwitchPreference; 43 44 import com.android.settings.datausage.DataSaverBackend; 45 import com.android.settings.search.BaseSearchIndexProvider; 46 import com.android.settings.search.Indexable; 47 import com.android.settings.wifi.tether.WifiTetherPreferenceController; 48 import com.android.settingslib.TetherUtil; 49 import com.android.settingslib.search.SearchIndexable; 50 51 import java.lang.ref.WeakReference; 52 import java.util.ArrayList; 53 import java.util.Arrays; 54 import java.util.List; 55 import java.util.concurrent.atomic.AtomicReference; 56 57 /* 58 * Displays preferences for Tethering. 59 */ 60 @SearchIndexable 61 public class TetherSettings extends RestrictedSettingsFragment 62 implements DataSaverBackend.Listener { 63 64 @VisibleForTesting 65 static final String KEY_TETHER_PREFS_SCREEN = "tether_prefs_screen"; 66 @VisibleForTesting 67 static final String KEY_WIFI_TETHER = "wifi_tether"; 68 @VisibleForTesting 69 static final String KEY_USB_TETHER_SETTINGS = "usb_tether_settings"; 70 @VisibleForTesting 71 static final String KEY_ENABLE_BLUETOOTH_TETHERING = "enable_bluetooth_tethering"; 72 private static final String KEY_DATA_SAVER_FOOTER = "disabled_on_data_saver"; 73 74 private static final String TAG = "TetheringSettings"; 75 76 private SwitchPreference mUsbTether; 77 78 private SwitchPreference mBluetoothTether; 79 80 private BroadcastReceiver mTetherChangeReceiver; 81 82 private String[] mUsbRegexs; 83 private String[] mBluetoothRegexs; 84 private AtomicReference<BluetoothPan> mBluetoothPan = new AtomicReference<>(); 85 86 private Handler mHandler = new Handler(); 87 private OnStartTetheringCallback mStartTetheringCallback; 88 private ConnectivityManager mCm; 89 90 private WifiTetherPreferenceController mWifiTetherPreferenceController; 91 92 private boolean mUsbConnected; 93 private boolean mMassStorageActive; 94 95 private boolean mBluetoothEnableForTether; 96 private boolean mUnavailable; 97 98 private DataSaverBackend mDataSaverBackend; 99 private boolean mDataSaverEnabled; 100 private Preference mDataSaverFooter; 101 102 @Override getMetricsCategory()103 public int getMetricsCategory() { 104 return SettingsEnums.TETHER; 105 } 106 TetherSettings()107 public TetherSettings() { 108 super(UserManager.DISALLOW_CONFIG_TETHERING); 109 } 110 111 @Override onAttach(Context context)112 public void onAttach(Context context) { 113 super.onAttach(context); 114 mWifiTetherPreferenceController = 115 new WifiTetherPreferenceController(context, getSettingsLifecycle()); 116 } 117 118 @Override onCreate(Bundle icicle)119 public void onCreate(Bundle icicle) { 120 super.onCreate(icicle); 121 122 addPreferencesFromResource(R.xml.tether_prefs); 123 mFooterPreferenceMixin.createFooterPreference() 124 .setTitle(R.string.tethering_footer_info); 125 126 mDataSaverBackend = new DataSaverBackend(getContext()); 127 mDataSaverEnabled = mDataSaverBackend.isDataSaverEnabled(); 128 mDataSaverFooter = findPreference(KEY_DATA_SAVER_FOOTER); 129 130 setIfOnlyAvailableForAdmins(true); 131 if (isUiRestricted()) { 132 mUnavailable = true; 133 getPreferenceScreen().removeAll(); 134 return; 135 } 136 137 final Activity activity = getActivity(); 138 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 139 if (adapter != null) { 140 adapter.getProfileProxy(activity.getApplicationContext(), mProfileServiceListener, 141 BluetoothProfile.PAN); 142 } 143 144 mUsbTether = (SwitchPreference) findPreference(KEY_USB_TETHER_SETTINGS); 145 mBluetoothTether = (SwitchPreference) findPreference(KEY_ENABLE_BLUETOOTH_TETHERING); 146 147 mDataSaverBackend.addListener(this); 148 149 mCm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); 150 151 mUsbRegexs = mCm.getTetherableUsbRegexs(); 152 mBluetoothRegexs = mCm.getTetherableBluetoothRegexs(); 153 154 final boolean usbAvailable = mUsbRegexs.length != 0; 155 final boolean bluetoothAvailable = mBluetoothRegexs.length != 0; 156 157 if (!usbAvailable || Utils.isMonkeyRunning()) { 158 getPreferenceScreen().removePreference(mUsbTether); 159 } 160 161 mWifiTetherPreferenceController.displayPreference(getPreferenceScreen()); 162 163 if (!bluetoothAvailable) { 164 getPreferenceScreen().removePreference(mBluetoothTether); 165 } else { 166 BluetoothPan pan = mBluetoothPan.get(); 167 if (pan != null && pan.isTetheringOn()) { 168 mBluetoothTether.setChecked(true); 169 } else { 170 mBluetoothTether.setChecked(false); 171 } 172 } 173 // Set initial state based on Data Saver mode. 174 onDataSaverChanged(mDataSaverBackend.isDataSaverEnabled()); 175 } 176 177 @Override onDestroy()178 public void onDestroy() { 179 mDataSaverBackend.remListener(this); 180 181 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 182 BluetoothProfile profile = mBluetoothPan.getAndSet(null); 183 if (profile != null && adapter != null) { 184 adapter.closeProfileProxy(BluetoothProfile.PAN, profile); 185 } 186 187 super.onDestroy(); 188 } 189 190 @Override onDataSaverChanged(boolean isDataSaving)191 public void onDataSaverChanged(boolean isDataSaving) { 192 mDataSaverEnabled = isDataSaving; 193 mUsbTether.setEnabled(!mDataSaverEnabled); 194 mBluetoothTether.setEnabled(!mDataSaverEnabled); 195 mDataSaverFooter.setVisible(mDataSaverEnabled); 196 } 197 198 @Override onWhitelistStatusChanged(int uid, boolean isWhitelisted)199 public void onWhitelistStatusChanged(int uid, boolean isWhitelisted) { 200 } 201 202 @Override onBlacklistStatusChanged(int uid, boolean isBlacklisted)203 public void onBlacklistStatusChanged(int uid, boolean isBlacklisted) { 204 } 205 206 private class TetherChangeReceiver extends BroadcastReceiver { 207 @Override onReceive(Context content, Intent intent)208 public void onReceive(Context content, Intent intent) { 209 String action = intent.getAction(); 210 if (action.equals(ConnectivityManager.ACTION_TETHER_STATE_CHANGED)) { 211 // TODO - this should understand the interface types 212 ArrayList<String> available = intent.getStringArrayListExtra( 213 ConnectivityManager.EXTRA_AVAILABLE_TETHER); 214 ArrayList<String> active = intent.getStringArrayListExtra( 215 ConnectivityManager.EXTRA_ACTIVE_TETHER); 216 ArrayList<String> errored = intent.getStringArrayListExtra( 217 ConnectivityManager.EXTRA_ERRORED_TETHER); 218 updateState(available.toArray(new String[available.size()]), 219 active.toArray(new String[active.size()]), 220 errored.toArray(new String[errored.size()])); 221 } else if (action.equals(Intent.ACTION_MEDIA_SHARED)) { 222 mMassStorageActive = true; 223 updateState(); 224 } else if (action.equals(Intent.ACTION_MEDIA_UNSHARED)) { 225 mMassStorageActive = false; 226 updateState(); 227 } else if (action.equals(UsbManager.ACTION_USB_STATE)) { 228 mUsbConnected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false); 229 updateState(); 230 } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) { 231 if (mBluetoothEnableForTether) { 232 switch (intent 233 .getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) { 234 case BluetoothAdapter.STATE_ON: 235 startTethering(TETHERING_BLUETOOTH); 236 mBluetoothEnableForTether = false; 237 break; 238 239 case BluetoothAdapter.STATE_OFF: 240 case BluetoothAdapter.ERROR: 241 mBluetoothEnableForTether = false; 242 break; 243 244 default: 245 // ignore transition states 246 } 247 } 248 updateState(); 249 } 250 } 251 } 252 253 @Override onStart()254 public void onStart() { 255 super.onStart(); 256 257 if (mUnavailable) { 258 if (!isUiRestrictedByOnlyAdmin()) { 259 getEmptyTextView().setText(R.string.tethering_settings_not_available); 260 } 261 getPreferenceScreen().removeAll(); 262 return; 263 } 264 265 final Activity activity = getActivity(); 266 267 mStartTetheringCallback = new OnStartTetheringCallback(this); 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 290 updateState(); 291 } 292 293 @Override onStop()294 public void onStop() { 295 super.onStop(); 296 297 if (mUnavailable) { 298 return; 299 } 300 getActivity().unregisterReceiver(mTetherChangeReceiver); 301 mTetherChangeReceiver = null; 302 mStartTetheringCallback = null; 303 } 304 updateState()305 private void updateState() { 306 String[] available = mCm.getTetherableIfaces(); 307 String[] tethered = mCm.getTetheredIfaces(); 308 String[] errored = mCm.getTetheringErroredIfaces(); 309 updateState(available, tethered, errored); 310 } 311 updateState(String[] available, String[] tethered, String[] errored)312 private void updateState(String[] available, String[] tethered, 313 String[] errored) { 314 updateUsbState(available, tethered, errored); 315 updateBluetoothState(); 316 } 317 updateUsbState(String[] available, String[] tethered, String[] errored)318 private void updateUsbState(String[] available, String[] tethered, 319 String[] errored) { 320 boolean usbAvailable = mUsbConnected && !mMassStorageActive; 321 int usbError = ConnectivityManager.TETHER_ERROR_NO_ERROR; 322 for (String s : available) { 323 for (String regex : mUsbRegexs) { 324 if (s.matches(regex)) { 325 if (usbError == ConnectivityManager.TETHER_ERROR_NO_ERROR) { 326 usbError = mCm.getLastTetherError(s); 327 } 328 } 329 } 330 } 331 boolean usbTethered = false; 332 for (String s : tethered) { 333 for (String regex : mUsbRegexs) { 334 if (s.matches(regex)) usbTethered = true; 335 } 336 } 337 boolean usbErrored = false; 338 for (String s: errored) { 339 for (String regex : mUsbRegexs) { 340 if (s.matches(regex)) usbErrored = true; 341 } 342 } 343 344 if (usbTethered) { 345 mUsbTether.setEnabled(!mDataSaverEnabled); 346 mUsbTether.setChecked(true); 347 } else if (usbAvailable) { 348 mUsbTether.setEnabled(!mDataSaverEnabled); 349 mUsbTether.setChecked(false); 350 } else { 351 mUsbTether.setEnabled(false); 352 mUsbTether.setChecked(false); 353 } 354 } 355 updateBluetoothState()356 private void updateBluetoothState() { 357 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 358 if (adapter == null) { 359 return; 360 } 361 int btState = adapter.getState(); 362 if (btState == BluetoothAdapter.STATE_TURNING_OFF) { 363 mBluetoothTether.setEnabled(false); 364 } else if (btState == BluetoothAdapter.STATE_TURNING_ON) { 365 mBluetoothTether.setEnabled(false); 366 } else { 367 BluetoothPan bluetoothPan = mBluetoothPan.get(); 368 if (btState == BluetoothAdapter.STATE_ON && bluetoothPan != null 369 && bluetoothPan.isTetheringOn()) { 370 mBluetoothTether.setChecked(true); 371 mBluetoothTether.setEnabled(!mDataSaverEnabled); 372 } else { 373 mBluetoothTether.setEnabled(!mDataSaverEnabled); 374 mBluetoothTether.setChecked(false); 375 } 376 } 377 } 378 isProvisioningNeededButUnavailable(Context context)379 public static boolean isProvisioningNeededButUnavailable(Context context) { 380 return (TetherUtil.isProvisioningNeeded(context) 381 && !isIntentAvailable(context)); 382 } 383 isIntentAvailable(Context context)384 private static boolean isIntentAvailable(Context context) { 385 String[] provisionApp = context.getResources().getStringArray( 386 com.android.internal.R.array.config_mobile_hotspot_provision_app); 387 if (provisionApp.length < 2) { 388 return false; 389 } 390 final PackageManager packageManager = context.getPackageManager(); 391 Intent intent = new Intent(Intent.ACTION_MAIN); 392 intent.setClassName(provisionApp[0], provisionApp[1]); 393 394 return (packageManager.queryIntentActivities(intent, 395 PackageManager.MATCH_DEFAULT_ONLY).size() > 0); 396 } 397 startTethering(int choice)398 private void startTethering(int choice) { 399 if (choice == TETHERING_BLUETOOTH) { 400 // Turn on Bluetooth first. 401 BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter(); 402 if (adapter.getState() == BluetoothAdapter.STATE_OFF) { 403 mBluetoothEnableForTether = true; 404 adapter.enable(); 405 mBluetoothTether.setEnabled(false); 406 return; 407 } 408 } 409 410 mCm.startTethering(choice, true, mStartTetheringCallback, mHandler); 411 } 412 413 @Override onPreferenceTreeClick(Preference preference)414 public boolean onPreferenceTreeClick(Preference preference) { 415 if (preference == mUsbTether) { 416 if (mUsbTether.isChecked()) { 417 startTethering(TETHERING_USB); 418 } else { 419 mCm.stopTethering(TETHERING_USB); 420 } 421 } else if (preference == mBluetoothTether) { 422 if (mBluetoothTether.isChecked()) { 423 startTethering(TETHERING_BLUETOOTH); 424 } else { 425 mCm.stopTethering(TETHERING_BLUETOOTH); 426 } 427 } 428 429 return super.onPreferenceTreeClick(preference); 430 } 431 432 @Override getHelpResource()433 public int getHelpResource() { 434 return R.string.help_url_tether; 435 } 436 437 private BluetoothProfile.ServiceListener mProfileServiceListener = 438 new BluetoothProfile.ServiceListener() { 439 public void onServiceConnected(int profile, BluetoothProfile proxy) { 440 mBluetoothPan.set((BluetoothPan) proxy); 441 } 442 public void onServiceDisconnected(int profile) { 443 mBluetoothPan.set(null); 444 } 445 }; 446 447 public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 448 new BaseSearchIndexProvider() { 449 @Override 450 public List<SearchIndexableResource> getXmlResourcesToIndex( 451 Context context, boolean enabled) { 452 final SearchIndexableResource sir = new SearchIndexableResource(context); 453 sir.xmlResId = R.xml.tether_prefs; 454 return Arrays.asList(sir); 455 } 456 457 @Override 458 public List<String> getNonIndexableKeys(Context context) { 459 final List<String> keys = super.getNonIndexableKeys(context); 460 final ConnectivityManager cm = 461 context.getSystemService(ConnectivityManager.class); 462 463 if (!TetherUtil.isTetherAvailable(context)) { 464 keys.add(KEY_TETHER_PREFS_SCREEN); 465 keys.add(KEY_WIFI_TETHER); 466 } 467 468 final boolean usbAvailable = 469 cm.getTetherableUsbRegexs().length != 0; 470 if (!usbAvailable || Utils.isMonkeyRunning()) { 471 keys.add(KEY_USB_TETHER_SETTINGS); 472 } 473 474 final boolean bluetoothAvailable = 475 cm.getTetherableBluetoothRegexs().length != 0; 476 if (!bluetoothAvailable) { 477 keys.add(KEY_ENABLE_BLUETOOTH_TETHERING); 478 } 479 return keys; 480 } 481 }; 482 483 private static final class OnStartTetheringCallback extends 484 ConnectivityManager.OnStartTetheringCallback { 485 final WeakReference<TetherSettings> mTetherSettings; 486 OnStartTetheringCallback(TetherSettings settings)487 OnStartTetheringCallback(TetherSettings settings) { 488 mTetherSettings = new WeakReference<>(settings); 489 } 490 491 @Override onTetheringStarted()492 public void onTetheringStarted() { 493 update(); 494 } 495 496 @Override onTetheringFailed()497 public void onTetheringFailed() { 498 update(); 499 } 500 update()501 private void update() { 502 TetherSettings settings = mTetherSettings.get(); 503 if (settings != null) { 504 settings.updateState(); 505 } 506 } 507 } 508 } 509