• 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.systemui.bluetooth.qsdialog
18 
19 import android.bluetooth.BluetoothDevice
20 import android.content.Context
21 import com.android.settingslib.bluetooth.BluetoothUtils
22 import com.android.settingslib.bluetooth.CachedBluetoothDevice
23 import com.android.settingslib.bluetooth.LocalBluetoothManager
24 import com.android.systemui.res.R
25 
26 private val backgroundOn = R.drawable.settingslib_switch_bar_bg_on
27 private val backgroundOff = R.drawable.bluetooth_tile_dialog_bg_off
28 private val backgroundOffBusy = R.drawable.bluetooth_tile_dialog_bg_off_busy
29 private val connected = R.string.quick_settings_bluetooth_device_connected
30 private val audioSharing = R.string.quick_settings_bluetooth_device_audio_sharing
31 private val audioSharingAddIcon = R.drawable.ic_add
32 private val audioSharingOnGoingIcon = R.drawable.ic_check
33 private val saved = R.string.quick_settings_bluetooth_device_saved
34 private val actionAccessibilityLabelActivate =
35     R.string.accessibility_quick_settings_bluetooth_device_tap_to_activate
36 private val actionAccessibilityLabelDisconnect =
37     R.string.accessibility_quick_settings_bluetooth_device_tap_to_disconnect
38 
39 /** Factories to create different types of Bluetooth device items from CachedBluetoothDevice. */
40 abstract class DeviceItemFactory {
isFilterMatchednull41     abstract fun isFilterMatched(
42         context: Context,
43         cachedDevice: CachedBluetoothDevice,
44         isOngoingCall: Boolean,
45         audioSharingAvailable: Boolean,
46     ): Boolean
47 
48     // Overloaded that defaults audioSharingAvailable to false
49     fun isFilterMatched(
50         context: Context,
51         cachedDevice: CachedBluetoothDevice,
52         isOngoingCall: Boolean,
53     ): Boolean = isFilterMatched(context, cachedDevice, isOngoingCall, false)
54 
55     abstract fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem
56 
57     companion object {
58         @JvmStatic
59         fun createDeviceItem(
60             cachedDevice: CachedBluetoothDevice,
61             type: DeviceItemType,
62             connectionSummary: String,
63             background: Int,
64             actionAccessibilityLabel: String,
65             isActive: Boolean,
66             actionIconRes: Int = R.drawable.ic_settings_24dp,
67         ): DeviceItem {
68             return DeviceItem(
69                 type = type,
70                 cachedBluetoothDevice = cachedDevice,
71                 deviceName = cachedDevice.name,
72                 connectionSummary = connectionSummary,
73                 iconWithDescription =
74                     cachedDevice.drawableWithDescription.let { Pair(it.first, it.second) },
75                 background = background,
76                 isEnabled = !cachedDevice.isBusy,
77                 actionAccessibilityLabel = actionAccessibilityLabel,
78                 isActive = isActive,
79                 actionIconRes = actionIconRes,
80             )
81         }
82     }
83 }
84 
85 internal open class ActiveMediaDeviceItemFactory : DeviceItemFactory() {
isFilterMatchednull86     override fun isFilterMatched(
87         context: Context,
88         cachedDevice: CachedBluetoothDevice,
89         isOngoingCall: Boolean,
90         audioSharingAvailable: Boolean,
91     ): Boolean {
92         return BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
93             BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, isOngoingCall)
94     }
95 
createnull96     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
97         return createDeviceItem(
98             cachedDevice,
99             DeviceItemType.ACTIVE_MEDIA_BLUETOOTH_DEVICE,
100             cachedDevice.connectionSummary ?: "",
101             backgroundOn,
102             context.getString(actionAccessibilityLabelDisconnect),
103             isActive = true,
104         )
105     }
106 }
107 
108 internal class AudioSharingMediaDeviceItemFactory(
109     private val localBluetoothManager: LocalBluetoothManager
110 ) : DeviceItemFactory() {
isFilterMatchednull111     override fun isFilterMatched(
112         context: Context,
113         cachedDevice: CachedBluetoothDevice,
114         isOngoingCall: Boolean,
115         audioSharingAvailable: Boolean,
116     ): Boolean {
117         return audioSharingAvailable &&
118             !isOngoingCall &&
119             BluetoothUtils.hasConnectedBroadcastSource(cachedDevice, localBluetoothManager)
120     }
121 
createnull122     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
123         return createDeviceItem(
124             cachedDevice,
125             DeviceItemType.AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
126             cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
127                 ?: context.getString(audioSharing),
128             if (cachedDevice.isBusy) backgroundOffBusy else backgroundOn,
129             "",
130             isActive = !cachedDevice.isBusy,
131             actionIconRes = audioSharingOnGoingIcon,
132         )
133     }
134 }
135 
136 internal class AvailableAudioSharingMediaDeviceItemFactory(
137     private val localBluetoothManager: LocalBluetoothManager
138 ) : AvailableMediaDeviceItemFactory() {
isFilterMatchednull139     override fun isFilterMatched(
140         context: Context,
141         cachedDevice: CachedBluetoothDevice,
142         isOngoingCall: Boolean,
143         audioSharingAvailable: Boolean,
144     ): Boolean {
145         return audioSharingAvailable &&
146             !isOngoingCall &&
147             super.isFilterMatched(context, cachedDevice, false, true) &&
148             BluetoothUtils.isAvailableAudioSharingMediaBluetoothDevice(
149                 cachedDevice,
150                 localBluetoothManager,
151             )
152     }
153 
createnull154     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
155         return createDeviceItem(
156             cachedDevice,
157             DeviceItemType.AVAILABLE_AUDIO_SHARING_MEDIA_BLUETOOTH_DEVICE,
158             context.getString(
159                 R.string.quick_settings_bluetooth_device_audio_sharing_or_switch_active
160             ),
161             if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
162             "",
163             isActive = false,
164             actionIconRes = audioSharingAddIcon,
165         )
166     }
167 }
168 
169 internal class ActiveHearingDeviceItemFactory : ActiveMediaDeviceItemFactory() {
isFilterMatchednull170     override fun isFilterMatched(
171         context: Context,
172         cachedDevice: CachedBluetoothDevice,
173         isOngoingCall: Boolean,
174         audioSharingAvailable: Boolean,
175     ): Boolean {
176         return BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
177             BluetoothUtils.isAvailableHearingDevice(cachedDevice)
178     }
179 }
180 
181 open class AvailableMediaDeviceItemFactory : DeviceItemFactory() {
isFilterMatchednull182     override fun isFilterMatched(
183         context: Context,
184         cachedDevice: CachedBluetoothDevice,
185         isOngoingCall: Boolean,
186         audioSharingAvailable: Boolean,
187     ): Boolean {
188         return !BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
189             BluetoothUtils.isAvailableMediaBluetoothDevice(cachedDevice, isOngoingCall)
190     }
191 
createnull192     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
193         return createDeviceItem(
194             cachedDevice,
195             DeviceItemType.AVAILABLE_MEDIA_BLUETOOTH_DEVICE,
196             cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
197                 ?: context.getString(connected),
198             if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
199             context.getString(actionAccessibilityLabelActivate),
200             isActive = false,
201         )
202     }
203 }
204 
205 internal class AvailableHearingDeviceItemFactory : AvailableMediaDeviceItemFactory() {
isFilterMatchednull206     override fun isFilterMatched(
207         context: Context,
208         cachedDevice: CachedBluetoothDevice,
209         isOngoingCall: Boolean,
210         audioSharingAvailable: Boolean,
211     ): Boolean {
212         return !BluetoothUtils.isActiveMediaDevice(cachedDevice) &&
213             BluetoothUtils.isAvailableHearingDevice(cachedDevice)
214     }
215 }
216 
217 internal open class ConnectedDeviceItemFactory : DeviceItemFactory() {
isFilterMatchednull218     override fun isFilterMatched(
219         context: Context,
220         cachedDevice: CachedBluetoothDevice,
221         isOngoingCall: Boolean,
222         audioSharingAvailable: Boolean,
223     ): Boolean {
224         return !BluetoothUtils.isExclusivelyManagedBluetoothDevice(context, cachedDevice.device) &&
225             BluetoothUtils.isConnectedBluetoothDevice(cachedDevice, isOngoingCall)
226     }
227 
createnull228     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
229         return createDeviceItem(
230             cachedDevice,
231             DeviceItemType.CONNECTED_BLUETOOTH_DEVICE,
232             cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
233                 ?: context.getString(connected),
234             if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
235             context.getString(actionAccessibilityLabelDisconnect),
236             isActive = false,
237         )
238     }
239 }
240 
241 internal class ConnectedHearingDeviceItemFactory : ConnectedDeviceItemFactory() {
isFilterMatchednull242     override fun isFilterMatched(
243         context: Context,
244         cachedDevice: CachedBluetoothDevice,
245         isOngoingCall: Boolean,
246         audioSharingAvailable: Boolean,
247     ): Boolean {
248         return cachedDevice.isHearingDevice &&
249             cachedDevice.bondState == BluetoothDevice.BOND_BONDED &&
250             cachedDevice.device.isConnected
251     }
252 }
253 
254 internal open class SavedDeviceItemFactory : DeviceItemFactory() {
isFilterMatchednull255     override fun isFilterMatched(
256         context: Context,
257         cachedDevice: CachedBluetoothDevice,
258         isOngoingCall: Boolean,
259         audioSharingAvailable: Boolean,
260     ): Boolean {
261         return !BluetoothUtils.isExclusivelyManagedBluetoothDevice(context, cachedDevice.device) &&
262             cachedDevice.bondState == BluetoothDevice.BOND_BONDED &&
263             !cachedDevice.isConnected
264     }
265 
createnull266     override fun create(context: Context, cachedDevice: CachedBluetoothDevice): DeviceItem {
267         return createDeviceItem(
268             cachedDevice,
269             DeviceItemType.SAVED_BLUETOOTH_DEVICE,
270             cachedDevice.connectionSummary.takeUnless { it.isNullOrEmpty() }
271                 ?: context.getString(saved),
272             if (cachedDevice.isBusy) backgroundOffBusy else backgroundOff,
273             context.getString(actionAccessibilityLabelActivate),
274             isActive = false,
275         )
276     }
277 }
278 
279 internal class SavedHearingDeviceItemFactory : SavedDeviceItemFactory() {
isFilterMatchednull280     override fun isFilterMatched(
281         context: Context,
282         cachedDevice: CachedBluetoothDevice,
283         isOngoingCall: Boolean,
284         audioSharingAvailable: Boolean,
285     ): Boolean {
286         return !BluetoothUtils.isExclusivelyManagedBluetoothDevice(
287             context,
288             cachedDevice.getDevice(),
289         ) &&
290             cachedDevice.isHearingDevice &&
291             cachedDevice.bondState == BluetoothDevice.BOND_BONDED &&
292             !cachedDevice.isConnected
293     }
294 }
295