Merge "[Catalyst] Migrate BluetoothTetherSwitchPreference." into main

This commit is contained in:
Treehugger Robot
2024-12-06 11:10:52 +00:00
committed by Android (Google) Code Review
2 changed files with 246 additions and 10 deletions

View File

@@ -0,0 +1,226 @@
/*
* Copyright (C) 2024 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.tether
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.net.ConnectivityManager
import android.net.TetheringManager
import android.os.Handler
import android.os.Looper
import com.android.settings.R
import com.android.settings.datausage.DataSaverBackend
import com.android.settingslib.datastore.KeyValueStore
import com.android.settingslib.datastore.KeyedDataObservable
import com.android.settingslib.metadata.PreferenceAvailabilityProvider
import com.android.settingslib.metadata.PreferenceLifecycleContext
import com.android.settingslib.metadata.PreferenceLifecycleProvider
import com.android.settingslib.metadata.ReadWritePermit
import com.android.settingslib.metadata.SensitivityLevel
import com.android.settingslib.metadata.SwitchPreference
import java.util.concurrent.atomic.AtomicReference
// LINT.IfChange
@Suppress("DEPRECATION")
class BluetoothTetherSwitchPreference :
SwitchPreference(KEY, R.string.bluetooth_tether_checkbox_text),
PreferenceAvailabilityProvider,
PreferenceLifecycleProvider {
private var tetherChangeReceiver: BroadcastReceiver? = null
override val summary: Int
get() = R.string.bluetooth_tethering_subtext
override val keywords: Int
get() = R.string.keywords_hotspot_tethering
override fun storage(context: Context): KeyValueStore = BluetoothTetherStore(context)
override fun isAvailable(context: Context): Boolean {
BluetoothAdapter.getDefaultAdapter() ?: return false
val tetheringManager = context.getSystemService(TetheringManager::class.java)
val bluetoothRegexs = tetheringManager?.tetherableBluetoothRegexs
return bluetoothRegexs?.isNotEmpty() == true
}
override fun isEnabled(context: Context): Boolean {
val adapter = BluetoothAdapter.getDefaultAdapter() ?: return false
val btState = adapter.state
/* TODO: when bluetooth is off, btstate will be `state_turning_on` -> `state_off` ->
`state_turning_on` -> `state_on`, causing preference enable status incorrect. */
when (btState) {
BluetoothAdapter.STATE_TURNING_OFF,
BluetoothAdapter.STATE_TURNING_ON -> return false
else -> {}
}
val dataSaverBackend = DataSaverBackend(context)
return !dataSaverBackend.isDataSaverEnabled
}
override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) =
ReadWritePermit.ALLOW
override val sensitivityLevel: Int
get() = SensitivityLevel.LOW_SENSITIVITY
override fun onCreate(context: PreferenceLifecycleContext) {
val receiver =
object : BroadcastReceiver() {
override fun onReceive(content: Context, intent: Intent) {
when (intent.action) {
TetheringManager.ACTION_TETHER_STATE_CHANGED,
Intent.ACTION_MEDIA_SHARED,
Intent.ACTION_MEDIA_UNSHARED,
BluetoothAdapter.ACTION_STATE_CHANGED,
BluetoothPan.ACTION_TETHERING_STATE_CHANGED ->
context.notifyPreferenceChange(KEY)
}
}
}
tetherChangeReceiver = receiver
var filter = IntentFilter(TetheringManager.ACTION_TETHER_STATE_CHANGED)
val intent = context.registerReceiver(receiver, filter)
filter = IntentFilter()
filter.addAction(Intent.ACTION_MEDIA_SHARED)
filter.addAction(Intent.ACTION_MEDIA_UNSHARED)
filter.addDataScheme("file")
context.registerReceiver(receiver, filter)
filter = IntentFilter()
filter.addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
filter.addAction(BluetoothPan.ACTION_TETHERING_STATE_CHANGED)
context.registerReceiver(receiver, filter)
}
override fun onDestroy(context: PreferenceLifecycleContext) {
tetherChangeReceiver?.let {
context.unregisterReceiver(it)
tetherChangeReceiver = null
}
}
@Suppress("UNCHECKED_CAST")
private class BluetoothTetherStore(private val context: Context) :
KeyedDataObservable<String>(), KeyValueStore {
val bluetoothPan = AtomicReference<BluetoothPan>()
override fun contains(key: String) = key == KEY
override fun <T : Any> getValue(key: String, valueType: Class<T>): T? {
// TODO: support async operation in background thread
val adapter = BluetoothAdapter.getDefaultAdapter() ?: return false as T
if (bluetoothPan.get() == null) {
val profileServiceListener: BluetoothProfile.ServiceListener =
object : BluetoothProfile.ServiceListener {
override fun onServiceConnected(profile: Int, proxy: BluetoothProfile) {
if (bluetoothPan.get() == null) {
bluetoothPan.set(proxy as BluetoothPan)
notifyChange(KEY, 0)
}
}
override fun onServiceDisconnected(profile: Int) {
/* Do nothing */
}
}
// TODO: adapter.closeProfileProxy(bluetoothPan.get())
adapter.getProfileProxy(
context.applicationContext,
profileServiceListener,
BluetoothProfile.PAN,
)
}
val btState = adapter.state
val pan = bluetoothPan.get()
return ((btState == BluetoothAdapter.STATE_ON ||
btState == BluetoothAdapter.STATE_TURNING_OFF) && pan != null && pan.isTetheringOn)
as T?
}
override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) {
if (value == null) return
val connectivityManager =
context.getSystemService(ConnectivityManager::class.java) ?: return
if (value as Boolean) {
val handler by lazy { Handler(Looper.getMainLooper()) }
val startTetheringCallback = OnStartTetheringCallback()
fun startTethering() {
connectivityManager.startTethering(
ConnectivityManager.TETHERING_BLUETOOTH,
true,
startTetheringCallback,
handler,
)
}
val adapter = BluetoothAdapter.getDefaultAdapter()
if (adapter.state == BluetoothAdapter.STATE_OFF) {
adapter.enable()
val filter = IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED)
val tetherChangeReceiver =
object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (
intent.getIntExtra(
BluetoothAdapter.EXTRA_STATE,
BluetoothAdapter.ERROR,
) == BluetoothAdapter.STATE_ON
) {
startTethering()
context.unregisterReceiver(this)
}
}
}
val intent = context.registerReceiver(tetherChangeReceiver, filter)
if (intent != null) tetherChangeReceiver.onReceive(context, intent)
} else {
startTethering()
}
} else {
connectivityManager.stopTethering(ConnectivityManager.TETHERING_BLUETOOTH)
}
}
private inner class OnStartTetheringCallback :
ConnectivityManager.OnStartTetheringCallback() {
override fun onTetheringStarted() {
notifyChange(KEY, 0)
}
override fun onTetheringFailed() {
notifyChange(KEY, 0)
}
}
}
companion object {
const val KEY = "enable_bluetooth_tethering"
}
}
// LINT.ThenChange(TetherSettings.java)

View File

@@ -72,6 +72,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
// LINT.IfChange
/**
* Displays preferences for Tethering.
*/
@@ -154,6 +155,7 @@ public class TetherSettings extends RestrictedDashboardFragment
return R.xml.tether_prefs;
}
@SuppressWarnings("NullAway")
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -205,16 +207,19 @@ public class TetherSettings extends RestrictedDashboardFragment
mWifiTetherPreferenceController.displayPreference(getPreferenceScreen());
if (!bluetoothAvailable) {
getPreferenceScreen().removePreference(mBluetoothTether);
} else {
BluetoothPan pan = mBluetoothPan.get();
if (pan != null && pan.isTetheringOn()) {
mBluetoothTether.setChecked(true);
if (!isCatalystEnabled()) {
if (!bluetoothAvailable) {
mBluetoothTether.setVisible(false);
} else {
mBluetoothTether.setChecked(false);
BluetoothPan pan = mBluetoothPan.get();
if (pan != null && pan.isTetheringOn()) {
mBluetoothTether.setChecked(true);
} else {
mBluetoothTether.setChecked(false);
}
}
}
if (!ethernetAvailable) getPreferenceScreen().removePreference(mEthernetTether);
// Set initial state based on Data Saver mode.
onDataSaverChanged(mDataSaverBackend.isDataSaverEnabled());
@@ -263,7 +268,9 @@ public class TetherSettings extends RestrictedDashboardFragment
mDataSaverEnabled = isDataSaving;
mWifiTetherPreferenceController.setDataSaverEnabled(mDataSaverEnabled);
mUsbTether.setEnabled(!mDataSaverEnabled);
mBluetoothTether.setEnabled(!mDataSaverEnabled);
if (!isCatalystEnabled()) {
mBluetoothTether.setEnabled(!mDataSaverEnabled);
}
mEthernetTether.setEnabled(!mDataSaverEnabled);
mDataSaverFooter.setVisible(mDataSaverEnabled);
}
@@ -513,6 +520,8 @@ public class TetherSettings extends RestrictedDashboardFragment
}
private void updateBluetoothState() {
if (isCatalystEnabled()) return;
final int btState = getBluetoothState();
if (DEBUG) {
Log.d(TAG, "updateBluetoothState() btState : " + btState);
@@ -568,7 +577,7 @@ public class TetherSettings extends RestrictedDashboardFragment
}
private void startTethering(int choice) {
if (choice == TETHERING_BLUETOOTH) {
if (choice == TETHERING_BLUETOOTH && !isCatalystEnabled()) {
// Turn on Bluetooth first.
BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
if (adapter.getState() == BluetoothAdapter.STATE_OFF) {
@@ -590,7 +599,7 @@ public class TetherSettings extends RestrictedDashboardFragment
} else {
mCm.stopTethering(TETHERING_USB);
}
} else if (preference == mBluetoothTether) {
} else if (preference == mBluetoothTether && !isCatalystEnabled()) {
if (mBluetoothTether.isChecked()) {
startTethering(TETHERING_BLUETOOTH);
} else {
@@ -738,3 +747,4 @@ public class TetherSettings extends RestrictedDashboardFragment
return TetherScreen.KEY;
}
}
// LINT.ThenChange(BluetoothTetherSwitchPreference.kt)