Files
app_Settings/src/com/android/settings/TetherSettings.java
Chienyuan bad5898fb6 Don't update state after stop bluetooth tethering
After using ConnectivityManager.stopTethering(), TetheringSettings may
update state before PanService set mTetherOn to false. This makes the
toggle button checked again and user need click it twice to disable it.

Bug: 111578971
Test: on/off bluetooth tethering from settings UI
Change-Id: I6873d762d2a290080d844b7a44e54f4bd532347e
2018-09-05 11:10:01 +08:00

509 lines
18 KiB
Java

/*
* Copyright (C) 2008 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.settings;
import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
import static android.net.ConnectivityManager.TETHERING_USB;
import android.app.Activity;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothPan;
import android.bluetooth.BluetoothProfile;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.hardware.usb.UsbManager;
import android.net.ConnectivityManager;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.UserManager;
import android.provider.SearchIndexableResource;
import androidx.annotation.VisibleForTesting;
import androidx.preference.Preference;
import androidx.preference.SwitchPreference;
import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
import com.android.settings.datausage.DataSaverBackend;
import com.android.settings.search.BaseSearchIndexProvider;
import com.android.settings.search.Indexable;
import com.android.settings.wifi.tether.WifiTetherPreferenceController;
import com.android.settingslib.TetherUtil;
import com.android.settingslib.search.SearchIndexable;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
/*
* Displays preferences for Tethering.
*/
@SearchIndexable
public class TetherSettings extends RestrictedSettingsFragment
implements DataSaverBackend.Listener {
@VisibleForTesting
static final String KEY_TETHER_PREFS_SCREEN = "tether_prefs_screen";
@VisibleForTesting
static final String KEY_WIFI_TETHER = "wifi_tether";
@VisibleForTesting
static final String KEY_USB_TETHER_SETTINGS = "usb_tether_settings";
@VisibleForTesting
static final String KEY_ENABLE_BLUETOOTH_TETHERING = "enable_bluetooth_tethering";
private static final String KEY_DATA_SAVER_FOOTER = "disabled_on_data_saver";
private static final String TAG = "TetheringSettings";
private SwitchPreference mUsbTether;
private SwitchPreference mBluetoothTether;
private BroadcastReceiver mTetherChangeReceiver;
private String[] mUsbRegexs;
private String[] mBluetoothRegexs;
private AtomicReference<BluetoothPan> mBluetoothPan = new AtomicReference<>();
private Handler mHandler = new Handler();
private OnStartTetheringCallback mStartTetheringCallback;
private ConnectivityManager mCm;
private WifiTetherPreferenceController mWifiTetherPreferenceController;
private boolean mUsbConnected;
private boolean mMassStorageActive;
private boolean mBluetoothEnableForTether;
private boolean mUnavailable;
private DataSaverBackend mDataSaverBackend;
private boolean mDataSaverEnabled;
private Preference mDataSaverFooter;
@Override
public int getMetricsCategory() {
return MetricsEvent.TETHER;
}
public TetherSettings() {
super(UserManager.DISALLOW_CONFIG_TETHERING);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
mWifiTetherPreferenceController =
new WifiTetherPreferenceController(context, getSettingsLifecycle());
}
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.tether_prefs);
mFooterPreferenceMixin.createFooterPreference()
.setTitle(R.string.tethering_footer_info);
mDataSaverBackend = new DataSaverBackend(getContext());
mDataSaverEnabled = mDataSaverBackend.isDataSaverEnabled();
mDataSaverFooter = findPreference(KEY_DATA_SAVER_FOOTER);
setIfOnlyAvailableForAdmins(true);
if (isUiRestricted()) {
mUnavailable = true;
getPreferenceScreen().removeAll();
return;
}
final Activity activity = getActivity();
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter != null) {
adapter.getProfileProxy(activity.getApplicationContext(), mProfileServiceListener,
BluetoothProfile.PAN);
}
mUsbTether = (SwitchPreference) findPreference(KEY_USB_TETHER_SETTINGS);
mBluetoothTether = (SwitchPreference) findPreference(KEY_ENABLE_BLUETOOTH_TETHERING);
mDataSaverBackend.addListener(this);
mCm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
mUsbRegexs = mCm.getTetherableUsbRegexs();
mBluetoothRegexs = mCm.getTetherableBluetoothRegexs();
final boolean usbAvailable = mUsbRegexs.length != 0;
final boolean bluetoothAvailable = mBluetoothRegexs.length != 0;
if (!usbAvailable || Utils.isMonkeyRunning()) {
getPreferenceScreen().removePreference(mUsbTether);
}
mWifiTetherPreferenceController.displayPreference(getPreferenceScreen());
if (!bluetoothAvailable) {
getPreferenceScreen().removePreference(mBluetoothTether);
} else {
BluetoothPan pan = mBluetoothPan.get();
if (pan != null && pan.isTetheringOn()) {
mBluetoothTether.setChecked(true);
} else {
mBluetoothTether.setChecked(false);
}
}
// Set initial state based on Data Saver mode.
onDataSaverChanged(mDataSaverBackend.isDataSaverEnabled());
}
@Override
public void onDestroy() {
mDataSaverBackend.remListener(this);
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
BluetoothProfile profile = mBluetoothPan.getAndSet(null);
if (profile != null && adapter != null) {
adapter.closeProfileProxy(BluetoothProfile.PAN, profile);
}
super.onDestroy();
}
@Override
public void onDataSaverChanged(boolean isDataSaving) {
mDataSaverEnabled = isDataSaving;
mUsbTether.setEnabled(!mDataSaverEnabled);
mBluetoothTether.setEnabled(!mDataSaverEnabled);
mDataSaverFooter.setVisible(mDataSaverEnabled);
}
@Override
public void onWhitelistStatusChanged(int uid, boolean isWhitelisted) {
}
@Override
public void onBlacklistStatusChanged(int uid, boolean isBlacklisted) {
}
private class TetherChangeReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context content, Intent intent) {
String action = intent.getAction();
if (action.equals(ConnectivityManager.ACTION_TETHER_STATE_CHANGED)) {
// TODO - this should understand the interface types
ArrayList<String> available = intent.getStringArrayListExtra(
ConnectivityManager.EXTRA_AVAILABLE_TETHER);
ArrayList<String> active = intent.getStringArrayListExtra(
ConnectivityManager.EXTRA_ACTIVE_TETHER);
ArrayList<String> errored = intent.getStringArrayListExtra(
ConnectivityManager.EXTRA_ERRORED_TETHER);
updateState(available.toArray(new String[available.size()]),
active.toArray(new String[active.size()]),
errored.toArray(new String[errored.size()]));
} else if (action.equals(Intent.ACTION_MEDIA_SHARED)) {
mMassStorageActive = true;
updateState();
} else if (action.equals(Intent.ACTION_MEDIA_UNSHARED)) {
mMassStorageActive = false;
updateState();
} else if (action.equals(UsbManager.ACTION_USB_STATE)) {
mUsbConnected = intent.getBooleanExtra(UsbManager.USB_CONNECTED, false);
updateState();
} else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
if (mBluetoothEnableForTether) {
switch (intent
.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)) {
case BluetoothAdapter.STATE_ON:
startTethering(TETHERING_BLUETOOTH);
mBluetoothEnableForTether = false;
break;
case BluetoothAdapter.STATE_OFF:
case BluetoothAdapter.ERROR:
mBluetoothEnableForTether = false;
break;
default:
// ignore transition states
}
}
updateState();
}
}
}
@Override
public void onStart() {
super.onStart();
if (mUnavailable) {
if (!isUiRestrictedByOnlyAdmin()) {
getEmptyTextView().setText(R.string.tethering_settings_not_available);
}
getPreferenceScreen().removeAll();
return;
}
final Activity activity = getActivity();
mStartTetheringCallback = new OnStartTetheringCallback(this);
mMassStorageActive = Environment.MEDIA_SHARED.equals(Environment.getExternalStorageState());
mTetherChangeReceiver = new TetherChangeReceiver();
IntentFilter filter = new IntentFilter(ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
Intent intent = activity.registerReceiver(mTetherChangeReceiver, filter);
filter = new IntentFilter();
filter.addAction(UsbManager.ACTION_USB_STATE);
activity.registerReceiver(mTetherChangeReceiver, filter);
filter = new IntentFilter();
filter.addAction(Intent.ACTION_MEDIA_SHARED);
filter.addAction(Intent.ACTION_MEDIA_UNSHARED);
filter.addDataScheme("file");
activity.registerReceiver(mTetherChangeReceiver, filter);
filter = new IntentFilter();
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
activity.registerReceiver(mTetherChangeReceiver, filter);
if (intent != null) mTetherChangeReceiver.onReceive(activity, intent);
updateState();
}
@Override
public void onStop() {
super.onStop();
if (mUnavailable) {
return;
}
getActivity().unregisterReceiver(mTetherChangeReceiver);
mTetherChangeReceiver = null;
mStartTetheringCallback = null;
}
private void updateState() {
String[] available = mCm.getTetherableIfaces();
String[] tethered = mCm.getTetheredIfaces();
String[] errored = mCm.getTetheringErroredIfaces();
updateState(available, tethered, errored);
}
private void updateState(String[] available, String[] tethered,
String[] errored) {
updateUsbState(available, tethered, errored);
updateBluetoothState();
}
private void updateUsbState(String[] available, String[] tethered,
String[] errored) {
boolean usbAvailable = mUsbConnected && !mMassStorageActive;
int usbError = ConnectivityManager.TETHER_ERROR_NO_ERROR;
for (String s : available) {
for (String regex : mUsbRegexs) {
if (s.matches(regex)) {
if (usbError == ConnectivityManager.TETHER_ERROR_NO_ERROR) {
usbError = mCm.getLastTetherError(s);
}
}
}
}
boolean usbTethered = false;
for (String s : tethered) {
for (String regex : mUsbRegexs) {
if (s.matches(regex)) usbTethered = true;
}
}
boolean usbErrored = false;
for (String s: errored) {
for (String regex : mUsbRegexs) {
if (s.matches(regex)) usbErrored = true;
}
}
if (usbTethered) {
mUsbTether.setEnabled(!mDataSaverEnabled);
mUsbTether.setChecked(true);
} else if (usbAvailable) {
mUsbTether.setEnabled(!mDataSaverEnabled);
mUsbTether.setChecked(false);
} else {
mUsbTether.setEnabled(false);
mUsbTether.setChecked(false);
}
}
private void updateBluetoothState() {
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter == null) {
return;
}
int btState = adapter.getState();
if (btState == BluetoothAdapter.STATE_TURNING_OFF) {
mBluetoothTether.setEnabled(false);
} else if (btState == BluetoothAdapter.STATE_TURNING_ON) {
mBluetoothTether.setEnabled(false);
} else {
BluetoothPan bluetoothPan = mBluetoothPan.get();
if (btState == BluetoothAdapter.STATE_ON && bluetoothPan != null
&& bluetoothPan.isTetheringOn()) {
mBluetoothTether.setChecked(true);
mBluetoothTether.setEnabled(!mDataSaverEnabled);
} else {
mBluetoothTether.setEnabled(!mDataSaverEnabled);
mBluetoothTether.setChecked(false);
}
}
}
public static boolean isProvisioningNeededButUnavailable(Context context) {
return (TetherUtil.isProvisioningNeeded(context)
&& !isIntentAvailable(context));
}
private static boolean isIntentAvailable(Context context) {
String[] provisionApp = context.getResources().getStringArray(
com.android.internal.R.array.config_mobile_hotspot_provision_app);
if (provisionApp.length < 2) {
return false;
}
final PackageManager packageManager = context.getPackageManager();
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setClassName(provisionApp[0], provisionApp[1]);
return (packageManager.queryIntentActivities(intent,
PackageManager.MATCH_DEFAULT_ONLY).size() > 0);
}
private void startTethering(int choice) {
if (choice == TETHERING_BLUETOOTH) {
// Turn on Bluetooth first.
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter.getState() == BluetoothAdapter.STATE_OFF) {
mBluetoothEnableForTether = true;
adapter.enable();
mBluetoothTether.setEnabled(false);
return;
}
}
mCm.startTethering(choice, true, mStartTetheringCallback, mHandler);
}
@Override
public boolean onPreferenceTreeClick(Preference preference) {
if (preference == mUsbTether) {
if (mUsbTether.isChecked()) {
startTethering(TETHERING_USB);
} else {
mCm.stopTethering(TETHERING_USB);
}
} else if (preference == mBluetoothTether) {
if (mBluetoothTether.isChecked()) {
startTethering(TETHERING_BLUETOOTH);
} else {
mCm.stopTethering(TETHERING_BLUETOOTH);
}
}
return super.onPreferenceTreeClick(preference);
}
@Override
public int getHelpResource() {
return R.string.help_url_tether;
}
private BluetoothProfile.ServiceListener mProfileServiceListener =
new BluetoothProfile.ServiceListener() {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
mBluetoothPan.set((BluetoothPan) proxy);
}
public void onServiceDisconnected(int profile) {
mBluetoothPan.set(null);
}
};
public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
new BaseSearchIndexProvider() {
@Override
public List<SearchIndexableResource> getXmlResourcesToIndex(
Context context, boolean enabled) {
final SearchIndexableResource sir = new SearchIndexableResource(context);
sir.xmlResId = R.xml.tether_prefs;
return Arrays.asList(sir);
}
@Override
public List<String> getNonIndexableKeys(Context context) {
final List<String> keys = super.getNonIndexableKeys(context);
final ConnectivityManager cm =
context.getSystemService(ConnectivityManager.class);
if (!TetherUtil.isTetherAvailable(context)) {
keys.add(KEY_TETHER_PREFS_SCREEN);
keys.add(KEY_WIFI_TETHER);
}
final boolean usbAvailable =
cm.getTetherableUsbRegexs().length != 0;
if (!usbAvailable || Utils.isMonkeyRunning()) {
keys.add(KEY_USB_TETHER_SETTINGS);
}
final boolean bluetoothAvailable =
cm.getTetherableBluetoothRegexs().length != 0;
if (!bluetoothAvailable) {
keys.add(KEY_ENABLE_BLUETOOTH_TETHERING);
}
return keys;
}
};
private static final class OnStartTetheringCallback extends
ConnectivityManager.OnStartTetheringCallback {
final WeakReference<TetherSettings> mTetherSettings;
OnStartTetheringCallback(TetherSettings settings) {
mTetherSettings = new WeakReference<>(settings);
}
@Override
public void onTetheringStarted() {
update();
}
@Override
public void onTetheringFailed() {
update();
}
private void update() {
TetherSettings settings = mTetherSettings.get();
if (settings != null) {
settings.updateState();
}
}
}
}