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