From 2b21d69c0cf66bc32b1c369a1f19df0bb7fba686 Mon Sep 17 00:00:00 2001 From: Jacky Wang Date: Mon, 9 Dec 2024 07:47:42 +0800 Subject: [PATCH] [Catalyst] Fully migrate BluetoothMainSwitchPreference Bug: 372774767 Flag: com.android.settings.flags.catalyst_bluetooth_switchbar_screen Test: atest Change-Id: Ie7a78a0ef2a8739d30ece4c1c4fde5651876dc8a --- .../BluetoothMainSwitchPreference.kt | 99 ---------- .../connecteddevice/BluetoothPreference.kt | 175 ++++++++++++++++++ ...enceTest.kt => BluetoothPreferenceTest.kt} | 19 +- 3 files changed, 183 insertions(+), 110 deletions(-) delete mode 100644 src/com/android/settings/connecteddevice/BluetoothMainSwitchPreference.kt create mode 100644 src/com/android/settings/connecteddevice/BluetoothPreference.kt rename tests/robotests/src/com/android/settings/connecteddevice/{BluetoothMainSwitchPreferenceTest.kt => BluetoothPreferenceTest.kt} (75%) diff --git a/src/com/android/settings/connecteddevice/BluetoothMainSwitchPreference.kt b/src/com/android/settings/connecteddevice/BluetoothMainSwitchPreference.kt deleted file mode 100644 index bf806531135..00000000000 --- a/src/com/android/settings/connecteddevice/BluetoothMainSwitchPreference.kt +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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.connecteddevice - -import android.bluetooth.BluetoothAdapter -import android.content.BroadcastReceiver -import android.content.Context -import android.content.Intent -import android.content.IntentFilter -import com.android.settings.R -import com.android.settings.widget.MainSwitchBarMetadata -import com.android.settingslib.datastore.KeyValueStore -import com.android.settingslib.datastore.NoOpKeyedObservable -import com.android.settingslib.metadata.PreferenceLifecycleContext -import com.android.settingslib.metadata.PreferenceLifecycleProvider -import com.android.settingslib.metadata.ReadWritePermit - -class BluetoothMainSwitchPreference(private val bluetoothAdapter: BluetoothAdapter?) : - MainSwitchBarMetadata, PreferenceLifecycleProvider { - - private lateinit var broadcastReceiver: BroadcastReceiver - - override val key - get() = "use_bluetooth" - - override val title - get() = R.string.bluetooth_main_switch_title - - 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 fun storage(context: Context) = BluetoothStateStore(bluetoothAdapter) - - override fun onStart(context: PreferenceLifecycleContext) { - broadcastReceiver = - object : BroadcastReceiver() { - override fun onReceive(receiverContext: Context, intent: Intent) { - context.notifyPreferenceChange(key) - } - } - context.registerReceiver( - broadcastReceiver, - IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED), - Context.RECEIVER_EXPORTED_UNAUDITED - ) - } - - override fun onStop(context: PreferenceLifecycleContext) { - if (::broadcastReceiver.isInitialized) { - context.unregisterReceiver(broadcastReceiver) - } - } - - override fun isEnabled(context: Context): Boolean { - return bluetoothAdapter?.state.let { - it == BluetoothAdapter.STATE_ON || it == BluetoothAdapter.STATE_OFF - } - } - - @Suppress("UNCHECKED_CAST") - class BluetoothStateStore(private val bluetoothAdapter: BluetoothAdapter?) : - NoOpKeyedObservable(), KeyValueStore { - - override fun contains(key: String) = true - - override fun getValue(key: String, valueType: Class): T? { - return (bluetoothAdapter?.state.let { - it == BluetoothAdapter.STATE_ON || it == BluetoothAdapter.STATE_TURNING_ON - }) as T - } - - override fun setValue(key: String, valueType: Class, value: T?) { - if (value is Boolean) { - if (value) { - bluetoothAdapter?.enable() - } else { - bluetoothAdapter?.disable() - } - } - } - } -} diff --git a/src/com/android/settings/connecteddevice/BluetoothPreference.kt b/src/com/android/settings/connecteddevice/BluetoothPreference.kt new file mode 100644 index 00000000000..c9b39537331 --- /dev/null +++ b/src/com/android/settings/connecteddevice/BluetoothPreference.kt @@ -0,0 +1,175 @@ +/* + * 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.connecteddevice + +import android.annotation.SuppressLint +import android.bluetooth.BluetoothAdapter +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.UserManager +import android.provider.Settings +import android.widget.Toast +import androidx.preference.Preference +import com.android.settings.PreferenceRestrictionMixin +import com.android.settings.R +import com.android.settings.network.SatelliteRepository.Companion.isSatelliteOn +import com.android.settings.network.SatelliteWarningDialogActivity +import com.android.settings.widget.MainSwitchBarMetadata +import com.android.settingslib.WirelessUtils +import com.android.settingslib.datastore.AbstractKeyedDataObservable +import com.android.settingslib.datastore.DataChangeReason +import com.android.settingslib.datastore.KeyValueStore +import com.android.settingslib.metadata.PreferenceMetadata +import com.android.settingslib.metadata.ReadWritePermit +import com.android.settingslib.metadata.SensitivityLevel + +@SuppressLint("MissingPermission") +class BluetoothPreference(private val bluetoothDataStore: BluetoothDataStore) : + MainSwitchBarMetadata, PreferenceRestrictionMixin, Preference.OnPreferenceChangeListener { + + override val key + get() = KEY + + override val title + get() = R.string.bluetooth_main_switch_title + + override val restrictionKeys: Array + get() = arrayOf(UserManager.DISALLOW_BLUETOOTH, UserManager.DISALLOW_CONFIG_BLUETOOTH) + + override fun getReadPermit(context: Context, myUid: Int, callingUid: Int) = + ReadWritePermit.ALLOW + + override fun getWritePermit(context: Context, value: Boolean?, myUid: Int, callingUid: Int) = + when { + isSatelliteOn(context, 3000) || + (value == true && + !WirelessUtils.isRadioAllowed(context, Settings.Global.RADIO_BLUETOOTH)) -> + ReadWritePermit.DISALLOW + else -> ReadWritePermit.ALLOW + } + + override val sensitivityLevel + get() = SensitivityLevel.LOW_SENSITIVITY + + override fun storage(context: Context) = bluetoothDataStore + + override fun isEnabled(context: Context): Boolean { + return super.isEnabled(context) && + bluetoothDataStore.bluetoothAdapter?.state.let { + it == BluetoothAdapter.STATE_ON || it == BluetoothAdapter.STATE_OFF + } + } + + override fun bind(preference: Preference, metadata: PreferenceMetadata) { + super.bind(preference, metadata) + preference.onPreferenceChangeListener = this + } + + override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { + val context = preference.context + + if (isSatelliteOn(context, 3000)) { + context.startActivity( + Intent(context, SatelliteWarningDialogActivity::class.java) + .putExtra( + SatelliteWarningDialogActivity.EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG, + SatelliteWarningDialogActivity.TYPE_IS_BLUETOOTH, + ) + ) + return false + } + + // Show toast message if Bluetooth is not allowed in airplane mode + if ( + newValue == true && + !WirelessUtils.isRadioAllowed(context, Settings.Global.RADIO_BLUETOOTH) + ) { + Toast.makeText(context, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show() + return false + } + + return true + } + + @Suppress("UNCHECKED_CAST") + private class BluetoothStorage( + private val context: Context, + override val bluetoothAdapter: BluetoothAdapter?, + ) : AbstractKeyedDataObservable(), BluetoothDataStore { + + private var broadcastReceiver: BroadcastReceiver? = null + + override fun contains(key: String) = key == KEY && bluetoothAdapter != null + + override fun getValue(key: String, valueType: Class): T { + return (bluetoothAdapter?.state.let { + it == BluetoothAdapter.STATE_ON || it == BluetoothAdapter.STATE_TURNING_ON + }) + as T + } + + @Suppress("DEPRECATION") + override fun setValue(key: String, valueType: Class, value: T?) { + if (value is Boolean) { + if (value) { + bluetoothAdapter?.enable() + } else { + bluetoothAdapter?.disable() + } + } + } + + @SuppressLint("WrongConstant") + override fun onFirstObserverAdded() { + broadcastReceiver = + object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + notifyChange(KEY, DataChangeReason.UPDATE) + } + } + context.registerReceiver( + broadcastReceiver, + IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED), + Context.RECEIVER_EXPORTED_UNAUDITED, + ) + } + + override fun onLastObserverRemoved() { + context.unregisterReceiver(broadcastReceiver) + } + } + + companion object { + const val KEY = "use_bluetooth" + + @Suppress("DEPRECATION") + fun createDataStore(context: Context) = + createDataStore(context, BluetoothAdapter.getDefaultAdapter()) + + fun createDataStore( + context: Context, + bluetoothAdapter: BluetoothAdapter?, + ): BluetoothDataStore = BluetoothStorage(context, bluetoothAdapter) + } +} + +/** Datastore of the bluetooth preference. */ +interface BluetoothDataStore : KeyValueStore { + val bluetoothAdapter: BluetoothAdapter? +} diff --git a/tests/robotests/src/com/android/settings/connecteddevice/BluetoothMainSwitchPreferenceTest.kt b/tests/robotests/src/com/android/settings/connecteddevice/BluetoothPreferenceTest.kt similarity index 75% rename from tests/robotests/src/com/android/settings/connecteddevice/BluetoothMainSwitchPreferenceTest.kt rename to tests/robotests/src/com/android/settings/connecteddevice/BluetoothPreferenceTest.kt index 15db130795d..8b739dbc776 100644 --- a/tests/robotests/src/com/android/settings/connecteddevice/BluetoothMainSwitchPreferenceTest.kt +++ b/tests/robotests/src/com/android/settings/connecteddevice/BluetoothPreferenceTest.kt @@ -31,45 +31,42 @@ import org.mockito.kotlin.verify import org.mockito.kotlin.whenever @RunWith(AndroidJUnit4::class) -class BluetoothMainSwitchPreferenceTest { +class BluetoothPreferenceTest { @get:Rule val setFlagsRule = SetFlagsRule() private val context: Context = ApplicationProvider.getApplicationContext() private lateinit var bluetoothAdapter: BluetoothAdapter - private lateinit var bluetoothMainSwitchPreference: BluetoothMainSwitchPreference + private lateinit var bluetoothPreference: BluetoothPreference @Before fun setUp() { bluetoothAdapter = spy(BluetoothAdapter.getDefaultAdapter()) whenever(bluetoothAdapter.state).thenReturn(BluetoothAdapter.STATE_ON) - bluetoothMainSwitchPreference = BluetoothMainSwitchPreference(bluetoothAdapter) + bluetoothPreference = + BluetoothPreference(BluetoothPreference.createDataStore(context, bluetoothAdapter)) } @Test fun isEnabled_bluetoothOn_returnTrue() { - assertThat(bluetoothMainSwitchPreference.isEnabled(context)).isTrue() + assertThat(bluetoothPreference.isEnabled(context)).isTrue() } @Test fun isEnabled_bluetoothTurningOn_returnFalse() { whenever(bluetoothAdapter.state).thenReturn(BluetoothAdapter.STATE_TURNING_ON) - assertThat(bluetoothMainSwitchPreference.isEnabled(context)).isFalse() + assertThat(bluetoothPreference.isEnabled(context)).isFalse() } @Test fun storageSetOff_turnOff() { - bluetoothMainSwitchPreference - .storage(context) - .setBoolean(bluetoothMainSwitchPreference.key, false) + bluetoothPreference.storage(context).setBoolean(bluetoothPreference.key, false) verify(bluetoothAdapter).disable() } @Test fun storageSetOn_turnOn() { - bluetoothMainSwitchPreference - .storage(context) - .setBoolean(bluetoothMainSwitchPreference.key, true) + bluetoothPreference.storage(context).setBoolean(bluetoothPreference.key, true) verify(bluetoothAdapter).enable() }