Files
packages_apps_Settings/src/com/android/settings/network/TetherEnabler.java
Zhen Zhang 0c02d33818 Update SharedPreference values based on tethering state
TetherEnabler need to update tethering state of each tethering
interfaces to make sure other preferences that depends on these
SharedPreferences be consistent with UI.

Bug: 148968321
Test: TetherEnablerTest; CodeInspectionTest. Manully test, built and
flashed to crosshatch device.

Change-Id: Ie0be7748adf20e6fb0ff5489795b0ca0664b6323
2020-03-04 10:59:29 -08:00

447 lines
16 KiB
Java

/*
* Copyright (C) 2019 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.network;
import static android.net.ConnectivityManager.TETHERING_BLUETOOTH;
import static android.net.ConnectivityManager.TETHERING_USB;
import static android.net.ConnectivityManager.TETHERING_WIFI;
import static com.android.settings.AllInOneTetherSettings.DEDUP_POSTFIX;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.annotation.IntDef;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothPan;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.wifi.WifiManager;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.Nullable;
import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LifecycleObserver;
import androidx.lifecycle.OnLifecycleEvent;
import com.android.internal.annotations.VisibleForTesting;
import com.android.settings.datausage.DataSaverBackend;
import com.android.settings.widget.SwitchWidgetController;
import java.lang.annotation.Retention;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicReference;
/**
* TetherEnabler is a helper to manage Tethering switch on/off state. It turns on/off
* different types of tethering based on stored values in {@link SharedPreferences} and ensures
* tethering state updated by data saver state.
*
* This class is not designed for extending. It's extendable solely for the test purpose.
*/
public class TetherEnabler implements SwitchWidgetController.OnSwitchChangeListener,
DataSaverBackend.Listener, LifecycleObserver,
SharedPreferences.OnSharedPreferenceChangeListener {
private OnTetherStateUpdateListener mListener;
/**
* Interface definition for a callback to be invoked when the tethering has been updated.
*/
public interface OnTetherStateUpdateListener {
/**
* Called when the tethering state has changed.
*
* @param isTethering The new tethering state.
*/
void onTetherStateUpdated(boolean isTethering);
}
private static final String TAG = "TetherEnabler";
private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
@Retention(SOURCE)
@IntDef(
flag = true,
value = {TETHERING_OFF, TETHERING_WIFI_ON, TETHERING_USB_ON, TETHERING_BLUETOOTH_ON}
)
@interface TetheringState {}
private static final int TETHERING_OFF = 0;
private static final int TETHERING_WIFI_ON = 1;
private static final int TETHERING_USB_ON = 1 << 1;
private static final int TETHERING_BLUETOOTH_ON = 1 << 2;
public static final String SHARED_PREF = "tether_options";
// This KEY is used for a shared preference value, not for any displayed preferences.
public static final String KEY_ENABLE_WIFI_TETHERING = "enable_wifi_tethering";
public static final String WIFI_TETHER_DISABLE_KEY = "disable_wifi_tethering";
public static final String USB_TETHER_KEY = "enable_usb_tethering";
public static final String BLUETOOTH_TETHER_KEY = "enable_bluetooth_tethering" + DEDUP_POSTFIX;
private final SwitchWidgetController mSwitchWidgetController;
private final WifiManager mWifiManager;
private final ConnectivityManager mConnectivityManager;
private final DataSaverBackend mDataSaverBackend;
private boolean mDataSaverEnabled;
private final Context mContext;
@VisibleForTesting
ConnectivityManager.OnStartTetheringCallback mOnStartTetheringCallback;
private final AtomicReference<BluetoothPan> mBluetoothPan;
private final SharedPreferences mSharedPreferences;
private boolean mBluetoothEnableForTether;
private final BluetoothAdapter mBluetoothAdapter;
public TetherEnabler(Context context, SwitchWidgetController switchWidgetController,
AtomicReference<BluetoothPan> bluetoothPan) {
mContext = context;
mSwitchWidgetController = switchWidgetController;
mDataSaverBackend = new DataSaverBackend(context);
mSharedPreferences = context.getSharedPreferences(SHARED_PREF, Context.MODE_PRIVATE);
mConnectivityManager =
(ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
mBluetoothPan = bluetoothPan;
mDataSaverEnabled = mDataSaverBackend.isDataSaverEnabled();
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void onStart() {
mDataSaverBackend.addListener(this);
mSwitchWidgetController.setListener(this);
mSwitchWidgetController.startListening();
final IntentFilter filter = new IntentFilter(
ConnectivityManager.ACTION_TETHER_STATE_CHANGED);
filter.addAction(WifiManager.WIFI_AP_STATE_CHANGED_ACTION);
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED);
mContext.registerReceiver(mTetherChangeReceiver, filter);
mOnStartTetheringCallback = new OnStartTetheringCallback(this);
updateState(null/*tethered*/);
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void onResume() {
mSharedPreferences.registerOnSharedPreferenceChangeListener(this);
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void onPause() {
mSharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void onStop() {
mDataSaverBackend.remListener(this);
mSwitchWidgetController.stopListening();
mContext.unregisterReceiver(mTetherChangeReceiver);
}
public void setListener(@Nullable OnTetherStateUpdateListener listener) {
mListener = listener;
}
@VisibleForTesting
void updateState(@Nullable String[] tethered) {
int tetherState = getTetheringState(tethered);
if (DEBUG) {
Log.d(TAG, "updateState: " + tetherState);
}
setSwitchCheckedInternal(tetherState != TETHERING_OFF);
setSharedPreferencesInternal(tetherState);
mSwitchWidgetController.setEnabled(!mDataSaverEnabled);
if (mListener != null) {
mListener.onTetherStateUpdated(tetherState != TETHERING_OFF);
}
}
private void setSwitchCheckedInternal(boolean checked) {
mSwitchWidgetController.stopListening();
mSwitchWidgetController.setChecked(checked);
mSwitchWidgetController.startListening();
}
private void setSharedPreferencesInternal(@TetheringState int tetherState) {
if (tetherState == TETHERING_OFF) {
// We don't override user's preferences when tethering off.
return;
}
mSharedPreferences.unregisterOnSharedPreferenceChangeListener(this);
final SharedPreferences.Editor editor = mSharedPreferences.edit();
editor.putBoolean(KEY_ENABLE_WIFI_TETHERING, (tetherState & TETHERING_WIFI_ON) != 0);
editor.putBoolean(USB_TETHER_KEY, (tetherState & TETHERING_USB_ON) != 0);
editor.putBoolean(BLUETOOTH_TETHER_KEY, (tetherState & TETHERING_BLUETOOTH_ON) != 0);
editor.commit();
mSharedPreferences.registerOnSharedPreferenceChangeListener(this);
}
private @TetheringState int getTetheringState(@Nullable String[] tethered) {
int tetherState = TETHERING_OFF;
if (tethered == null) {
tethered = mConnectivityManager.getTetheredIfaces();
}
if (mWifiManager.getWifiApState() == WifiManager.WIFI_AP_STATE_ENABLED) {
tetherState |= TETHERING_WIFI_ON;
}
final BluetoothPan pan = mBluetoothPan.get();
if (pan != null && pan.isTetheringOn()) {
tetherState |= TETHERING_BLUETOOTH_ON;
}
String[] usbRegexs = mConnectivityManager.getTetherableUsbRegexs();
for (String s : tethered) {
for (String regex : usbRegexs) {
if (s.matches(regex)) {
return tetherState | TETHERING_USB_ON;
}
}
}
return tetherState;
}
@Override
public boolean onSwitchToggled(boolean isChecked) {
if (isChecked && getTetheringState(null /* tethered */) == TETHERING_OFF) {
startTether();
}
if (!isChecked && getTetheringState(null /* tethered */) != TETHERING_OFF) {
stopTether();
}
return true;
}
private void stopTether() {
// Wi-Fi tether is selected by default.
if (mSharedPreferences.getBoolean(KEY_ENABLE_WIFI_TETHERING, true)) {
stopTethering(TETHERING_WIFI);
}
if (mSharedPreferences.getBoolean(USB_TETHER_KEY, false)) {
stopTethering(TETHERING_USB);
}
if (mSharedPreferences.getBoolean(BLUETOOTH_TETHER_KEY, false)) {
stopTethering(TETHERING_BLUETOOTH);
}
}
/**
* Use this method to stop a single choice of tethering.
*
* @param choice The choice of tethering to stop.
*/
public void stopTethering(int choice) {
mConnectivityManager.stopTethering(choice);
}
@VisibleForTesting
void startTether() {
// Wi-Fi tether is selected by default.
if (mSharedPreferences.getBoolean(KEY_ENABLE_WIFI_TETHERING, true)) {
startTethering(TETHERING_WIFI);
}
if (mSharedPreferences.getBoolean(USB_TETHER_KEY, false)) {
startTethering(TETHERING_USB);
}
if (mSharedPreferences.getBoolean(BLUETOOTH_TETHER_KEY, false)) {
startTethering(TETHERING_BLUETOOTH);
}
}
/**
* Use this method to start a single choice of tethering.
* For bluetooth tethering, it will first turn on bluetooth if bluetooth is off.
* For Wi-Fi tethering, it will be no-op if Wi-Fi tethering already active.
*
* @param choice The choice of tethering to start.
*/
public void startTethering(int choice) {
mSwitchWidgetController.setEnabled(false);
if (choice == TETHERING_WIFI && mWifiManager.isWifiApEnabled()) {
if (DEBUG) {
Log.d(TAG, "Wifi tether already active!");
}
return;
}
if (choice == TETHERING_BLUETOOTH) {
if (mBluetoothAdapter.getState() == BluetoothAdapter.STATE_OFF) {
if (DEBUG) {
Log.d(TAG, "Turn on bluetooth first.");
}
mBluetoothEnableForTether = true;
mBluetoothAdapter.enable();
return;
}
}
mConnectivityManager.startTethering(choice, true /* showProvisioningUi */,
mOnStartTetheringCallback, new Handler(Looper.getMainLooper()));
}
private final BroadcastReceiver mTetherChangeReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
ArrayList<String> active = null;
boolean shouldUpdateState = false;
if (TextUtils.equals(ConnectivityManager.ACTION_TETHER_STATE_CHANGED, action)) {
active = intent.getStringArrayListExtra(ConnectivityManager.EXTRA_ACTIVE_TETHER);
shouldUpdateState = true;
} else if (TextUtils.equals(WifiManager.WIFI_AP_STATE_CHANGED_ACTION, action)) {
shouldUpdateState = handleWifiApStateChanged(intent.getIntExtra(
WifiManager.EXTRA_WIFI_AP_STATE, WifiManager.WIFI_AP_STATE_FAILED));
} else if (TextUtils.equals(BluetoothAdapter.ACTION_STATE_CHANGED, action)) {
shouldUpdateState = handleBluetoothStateChanged(intent
.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR));
}
if (shouldUpdateState) {
if (active != null) {
updateState(active.toArray(new String[0]));
} else {
updateState(null/*tethered*/);
}
}
}
};
private boolean handleBluetoothStateChanged(int state) {
switch (state) {
case BluetoothAdapter.STATE_ON:
if (mBluetoothEnableForTether) {
startTethering(TETHERING_BLUETOOTH);
}
// Fall through.
case BluetoothAdapter.STATE_OFF:
// Fall through.
case BluetoothAdapter.ERROR:
mBluetoothEnableForTether = false;
return true;
default:
// Return false for transition states.
return false;
}
}
private boolean handleWifiApStateChanged(int state) {
switch (state) {
case WifiManager.WIFI_AP_STATE_FAILED:
Log.e(TAG, "Wifi AP is failed!");
// fall through
case WifiManager.WIFI_AP_STATE_ENABLED:
// fall through
case WifiManager.WIFI_AP_STATE_DISABLED:
return true;
default:
// return false for transition state
return false;
}
}
@Override
public void onDataSaverChanged(boolean isDataSaving) {
mDataSaverEnabled = isDataSaving;
mSwitchWidgetController.setEnabled(!isDataSaving);
}
@Override
public void onWhitelistStatusChanged(int uid, boolean isWhitelisted) {
// we don't care, since we just want to read the value
}
@Override
public void onBlacklistStatusChanged(int uid, boolean isBlacklisted) {
// we don't care, since we just want to read the value
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (!mSwitchWidgetController.isChecked()) {
return;
}
if (TextUtils.equals(KEY_ENABLE_WIFI_TETHERING, key)) {
if (sharedPreferences.getBoolean(key, true)) {
startTethering(TETHERING_WIFI);
} else {
mConnectivityManager.stopTethering(TETHERING_WIFI);
}
} else if (TextUtils.equals(USB_TETHER_KEY, key)) {
if (sharedPreferences.getBoolean(key, false)) {
startTethering(TETHERING_USB);
} else {
mConnectivityManager.stopTethering(TETHERING_USB);
}
} else if (TextUtils.equals(BLUETOOTH_TETHER_KEY, key)) {
if (sharedPreferences.getBoolean(key, false)) {
startTethering(TETHERING_BLUETOOTH);
} else {
mConnectivityManager.stopTethering(TETHERING_BLUETOOTH);
}
}
}
private static final class OnStartTetheringCallback extends
ConnectivityManager.OnStartTetheringCallback {
final WeakReference<TetherEnabler> mTetherEnabler;
OnStartTetheringCallback(TetherEnabler enabler) {
mTetherEnabler = new WeakReference<>(enabler);
}
@Override
public void onTetheringStarted() {
update();
}
@Override
public void onTetheringFailed() {
update();
}
private void update() {
TetherEnabler enabler = mTetherEnabler.get();
if (enabler != null) {
enabler.updateState(null/*tethered*/);
}
}
}
}