• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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