/* * 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.Manifest import android.annotation.SuppressLint import android.app.settings.SettingsEnums.ACTION_SETTINGS_MASTER_SWITCH_BLUETOOTH_TOGGLE 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.R import com.android.settings.contract.KEY_BLUETOOTH import com.android.settings.metrics.PreferenceActionMetricsProvider import com.android.settings.network.SatelliteRepository.Companion.isSatelliteOn import com.android.settings.network.SatelliteWarningDialogActivity import com.android.settings.restriction.PreferenceRestrictionMixin import com.android.settings.widget.MainSwitchBarMetadata import com.android.settingslib.WirelessUtils import com.android.settingslib.datastore.AbstractKeyedDataObservable import com.android.settingslib.datastore.KeyValueStore import com.android.settingslib.datastore.Permissions import com.android.settingslib.metadata.PreferenceChangeReason 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, PreferenceActionMetricsProvider, PreferenceRestrictionMixin, Preference.OnPreferenceChangeListener { override val key get() = KEY override val title get() = R.string.bluetooth_main_switch_title override val preferenceActionMetrics: Int get() = ACTION_SETTINGS_MASTER_SWITCH_BLUETOOTH_TOGGLE override fun tags(context: Context) = arrayOf(KEY_BLUETOOTH) override val restrictionKeys: Array get() = arrayOf(UserManager.DISALLOW_BLUETOOTH, UserManager.DISALLOW_CONFIG_BLUETOOTH) override fun getReadPermissions(context: Context) = Permissions.EMPTY override fun getWritePermissions(context: Context) = Permissions.allOf(Manifest.permission.BLUETOOTH_CONNECT) override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) = ReadWritePermit.ALLOW override fun getWritePermit( context: Context, value: Boolean?, callingPid: 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) { val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR) if ( state == BluetoothAdapter.STATE_ON || state == BluetoothAdapter.STATE_OFF ) { notifyChange(KEY, PreferenceChangeReason.STATE) } } } 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? }