1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.connecteddevice 18 19 import android.Manifest 20 import android.annotation.SuppressLint 21 import android.app.settings.SettingsEnums.ACTION_SETTINGS_MASTER_SWITCH_BLUETOOTH_TOGGLE 22 import android.bluetooth.BluetoothAdapter 23 import android.content.BroadcastReceiver 24 import android.content.Context 25 import android.content.Intent 26 import android.content.IntentFilter 27 import android.os.UserManager 28 import android.provider.Settings 29 import android.widget.Toast 30 import androidx.preference.Preference 31 import com.android.settings.R 32 import com.android.settings.contract.KEY_BLUETOOTH 33 import com.android.settings.metrics.PreferenceActionMetricsProvider 34 import com.android.settings.network.SatelliteRepository.Companion.isSatelliteOn 35 import com.android.settings.network.SatelliteWarningDialogActivity 36 import com.android.settings.restriction.PreferenceRestrictionMixin 37 import com.android.settings.widget.MainSwitchBarMetadata 38 import com.android.settingslib.WirelessUtils 39 import com.android.settingslib.datastore.AbstractKeyedDataObservable 40 import com.android.settingslib.datastore.KeyValueStore 41 import com.android.settingslib.datastore.Permissions 42 import com.android.settingslib.metadata.PreferenceChangeReason 43 import com.android.settingslib.metadata.PreferenceMetadata 44 import com.android.settingslib.metadata.ReadWritePermit 45 import com.android.settingslib.metadata.SensitivityLevel 46 47 @SuppressLint("MissingPermission") 48 class BluetoothPreference(private val bluetoothDataStore: BluetoothDataStore) : 49 MainSwitchBarMetadata, 50 PreferenceActionMetricsProvider, 51 PreferenceRestrictionMixin, 52 Preference.OnPreferenceChangeListener { 53 54 override val key 55 get() = KEY 56 57 override val title 58 get() = R.string.bluetooth_main_switch_title 59 60 override val preferenceActionMetrics: Int 61 get() = ACTION_SETTINGS_MASTER_SWITCH_BLUETOOTH_TOGGLE 62 tagsnull63 override fun tags(context: Context) = arrayOf(KEY_BLUETOOTH) 64 65 override val restrictionKeys: Array<String> 66 get() = arrayOf(UserManager.DISALLOW_BLUETOOTH, UserManager.DISALLOW_CONFIG_BLUETOOTH) 67 68 override fun getReadPermissions(context: Context) = Permissions.EMPTY 69 70 override fun getWritePermissions(context: Context) = 71 Permissions.allOf(Manifest.permission.BLUETOOTH_CONNECT) 72 73 override fun getReadPermit(context: Context, callingPid: Int, callingUid: Int) = 74 ReadWritePermit.ALLOW 75 76 override fun getWritePermit( 77 context: Context, 78 value: Boolean?, 79 callingPid: Int, 80 callingUid: Int, 81 ) = 82 when { 83 isSatelliteOn(context, 3000) || 84 (value == true && 85 !WirelessUtils.isRadioAllowed(context, Settings.Global.RADIO_BLUETOOTH)) -> 86 ReadWritePermit.DISALLOW 87 else -> ReadWritePermit.ALLOW 88 } 89 90 override val sensitivityLevel 91 get() = SensitivityLevel.LOW_SENSITIVITY 92 storagenull93 override fun storage(context: Context) = bluetoothDataStore 94 95 override fun isEnabled(context: Context): Boolean { 96 return super<PreferenceRestrictionMixin>.isEnabled(context) && 97 bluetoothDataStore.bluetoothAdapter?.state.let { 98 it == BluetoothAdapter.STATE_ON || it == BluetoothAdapter.STATE_OFF 99 } 100 } 101 bindnull102 override fun bind(preference: Preference, metadata: PreferenceMetadata) { 103 super.bind(preference, metadata) 104 preference.onPreferenceChangeListener = this 105 } 106 onPreferenceChangenull107 override fun onPreferenceChange(preference: Preference, newValue: Any?): Boolean { 108 val context = preference.context 109 110 if (isSatelliteOn(context, 3000)) { 111 context.startActivity( 112 Intent(context, SatelliteWarningDialogActivity::class.java) 113 .putExtra( 114 SatelliteWarningDialogActivity.EXTRA_TYPE_OF_SATELLITE_WARNING_DIALOG, 115 SatelliteWarningDialogActivity.TYPE_IS_BLUETOOTH, 116 ) 117 ) 118 return false 119 } 120 121 // Show toast message if Bluetooth is not allowed in airplane mode 122 if ( 123 newValue == true && 124 !WirelessUtils.isRadioAllowed(context, Settings.Global.RADIO_BLUETOOTH) 125 ) { 126 Toast.makeText(context, R.string.wifi_in_airplane_mode, Toast.LENGTH_SHORT).show() 127 return false 128 } 129 130 return true 131 } 132 133 @Suppress("UNCHECKED_CAST") 134 private class BluetoothStorage( 135 private val context: Context, 136 override val bluetoothAdapter: BluetoothAdapter?, 137 ) : AbstractKeyedDataObservable<String>(), BluetoothDataStore { 138 139 private var broadcastReceiver: BroadcastReceiver? = null 140 containsnull141 override fun contains(key: String) = key == KEY && bluetoothAdapter != null 142 143 override fun <T : Any> getValue(key: String, valueType: Class<T>): T { 144 return (bluetoothAdapter?.state.let { 145 it == BluetoothAdapter.STATE_ON || it == BluetoothAdapter.STATE_TURNING_ON 146 }) 147 as T 148 } 149 150 @Suppress("DEPRECATION") setValuenull151 override fun <T : Any> setValue(key: String, valueType: Class<T>, value: T?) { 152 if (value is Boolean) { 153 if (value) { 154 bluetoothAdapter?.enable() 155 } else { 156 bluetoothAdapter?.disable() 157 } 158 } 159 } 160 161 @SuppressLint("WrongConstant") onFirstObserverAddednull162 override fun onFirstObserverAdded() { 163 broadcastReceiver = 164 object : BroadcastReceiver() { 165 override fun onReceive(context: Context, intent: Intent) { 166 val state = 167 intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR) 168 if ( 169 state == BluetoothAdapter.STATE_ON || 170 state == BluetoothAdapter.STATE_OFF 171 ) { 172 notifyChange(KEY, PreferenceChangeReason.STATE) 173 } 174 } 175 } 176 context.registerReceiver( 177 broadcastReceiver, 178 IntentFilter(BluetoothAdapter.ACTION_STATE_CHANGED), 179 Context.RECEIVER_EXPORTED_UNAUDITED, 180 ) 181 } 182 onLastObserverRemovednull183 override fun onLastObserverRemoved() { 184 context.unregisterReceiver(broadcastReceiver) 185 } 186 } 187 188 companion object { 189 const val KEY = "use_bluetooth" 190 191 @Suppress("DEPRECATION") createDataStorenull192 fun createDataStore(context: Context) = 193 createDataStore(context, BluetoothAdapter.getDefaultAdapter()) 194 195 fun createDataStore( 196 context: Context, 197 bluetoothAdapter: BluetoothAdapter?, 198 ): BluetoothDataStore = BluetoothStorage(context, bluetoothAdapter) 199 } 200 } 201 202 /** Datastore of the bluetooth preference. */ 203 interface BluetoothDataStore : KeyValueStore { 204 val bluetoothAdapter: BluetoothAdapter? 205 } 206