1 /*
<lambda>null2  * 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.settingslib.bluetooth.devicesettings.data.repository
18 
19 import android.bluetooth.BluetoothAdapter
20 import android.content.Context
21 import android.text.TextUtils
22 import com.android.settingslib.bluetooth.CachedBluetoothDevice
23 import com.android.settingslib.bluetooth.devicesettings.ActionSwitchPreference
24 import com.android.settingslib.bluetooth.devicesettings.DeviceSetting
25 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingAction
26 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingContract
27 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingFooterPreference
28 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingHelpPreference
29 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
30 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingIntentAction
31 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingItem
32 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingPendingIntentAction
33 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingsConfig
34 import com.android.settingslib.bluetooth.devicesettings.MultiTogglePreference
35 import com.android.settingslib.bluetooth.devicesettings.ToggleInfo
36 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingActionModel
37 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
38 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel.AppProvidedItem
39 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel.BuiltinItem.BluetoothProfilesItem
40 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem
41 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
42 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
43 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
44 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
45 import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
46 import com.google.common.cache.CacheBuilder
47 import com.google.common.cache.CacheLoader
48 import com.google.common.cache.LoadingCache
49 import kotlin.coroutines.CoroutineContext
50 import kotlinx.coroutines.CoroutineScope
51 import kotlinx.coroutines.flow.Flow
52 import kotlinx.coroutines.flow.map
53 import kotlinx.coroutines.launch
54 
55 /** Provides functionality to control bluetooth device settings. */
56 interface DeviceSettingRepository {
57     /** Gets config for the bluetooth device, returns null if failed. */
58     suspend fun getDeviceSettingsConfig(
59         cachedDevice: CachedBluetoothDevice
60     ): DeviceSettingConfigModel?
61 
62     /** Gets device setting for the bluetooth device. */
63     fun getDeviceSetting(
64         cachedDevice: CachedBluetoothDevice,
65         @DeviceSettingId settingId: Int,
66     ): Flow<DeviceSettingModel?>
67 }
68 
69 class DeviceSettingRepositoryImpl(
70     private val context: Context,
71     private val bluetoothAdaptor: BluetoothAdapter,
72     private val coroutineScope: CoroutineScope,
73     private val backgroundCoroutineContext: CoroutineContext,
74 ) : DeviceSettingRepository {
75     private val connectionCache:
76         LoadingCache<CachedBluetoothDevice, DeviceSettingServiceConnection> =
77         CacheBuilder.newBuilder()
78             .weakValues()
79             .build(
80                 object : CacheLoader<CachedBluetoothDevice, DeviceSettingServiceConnection>() {
loadnull81                     override fun load(
82                         cachedDevice: CachedBluetoothDevice
83                     ): DeviceSettingServiceConnection =
84                         DeviceSettingServiceConnection(
85                             cachedDevice,
86                             context,
87                             bluetoothAdaptor,
88                             coroutineScope,
89                             backgroundCoroutineContext,
90                         )
91                 }
92             )
93 
94     override suspend fun getDeviceSettingsConfig(
95         cachedDevice: CachedBluetoothDevice
96     ): DeviceSettingConfigModel? =
97         connectionCache.get(cachedDevice).getDeviceSettingsConfig()?.toModel()
98 
99     override fun getDeviceSetting(
100         cachedDevice: CachedBluetoothDevice,
101         settingId: Int,
102     ): Flow<DeviceSettingModel?> =
103         connectionCache.get(cachedDevice).let { connection ->
104             connection.getDeviceSetting(settingId).map { it?.toModel(cachedDevice, connection) }
105         }
106 
toModelnull107     private fun DeviceSettingsConfig.toModel(): DeviceSettingConfigModel =
108         DeviceSettingConfigModel(
109             mainItems = mainContentItems.toModel(),
110             moreSettingsItems = moreSettingsItems.toModel(),
111             moreSettingsHelpItem = moreSettingsHelpItem?.toModel(),
112         )
113 
114     private fun List<DeviceSettingItem>.toModel(): List<DeviceSettingConfigItemModel> {
115         return this.flatMap { item ->
116             if (item.settingId in EXPANDABLE_SETTING_IDS) {
117                 IntRange(item.settingId, item.settingId + SETTING_ID_EXPAND_LIMIT - 1).map {
118                     item.toModel(overrideSettingId = it)
119                 }
120             } else {
121                 listOf(item.toModel())
122             }
123         }
124     }
125 
toModelnull126     private fun DeviceSettingItem.toModel(
127         overrideSettingId: Int? = null
128     ): DeviceSettingConfigItemModel {
129         return if (!TextUtils.isEmpty(preferenceKey)) {
130             if (settingId == DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES) {
131                 BluetoothProfilesItem(
132                     overrideSettingId ?: settingId,
133                     highlighted,
134                     preferenceKey!!,
135                     extras.getStringArrayList(DeviceSettingContract.INVISIBLE_PROFILES)
136                         ?: emptyList(),
137                 )
138             } else {
139                 CommonBuiltinItem(overrideSettingId ?: settingId, highlighted, preferenceKey!!)
140             }
141         } else {
142             AppProvidedItem(overrideSettingId ?: settingId, highlighted)
143         }
144     }
145 
toModelnull146     private fun DeviceSettingAction.toModel(): DeviceSettingActionModel? =
147         when (this) {
148             is DeviceSettingIntentAction -> DeviceSettingActionModel.IntentAction(this.intent)
149             is DeviceSettingPendingIntentAction ->
150                 DeviceSettingActionModel.PendingIntentAction(this.pendingIntent)
151 
152             else -> null
153         }
154 
toModelnull155     private fun DeviceSetting.toModel(
156         cachedDevice: CachedBluetoothDevice,
157         connection: DeviceSettingServiceConnection,
158     ): DeviceSettingModel =
159         when (val pref = preference) {
160             is ActionSwitchPreference ->
161                 DeviceSettingModel.ActionSwitchPreference(
162                     cachedDevice = cachedDevice,
163                     id = settingId,
164                     title = pref.title,
165                     summary = pref.summary,
166                     icon = pref.icon?.let { DeviceSettingIcon.BitmapIcon(it) },
167                     isAllowedChangingState = pref.isAllowedChangingState,
168                     action = pref.action.toModel(),
169                     switchState =
170                         if (pref.hasSwitch()) {
171                             DeviceSettingStateModel.ActionSwitchPreferenceState(pref.checked)
172                         } else {
173                             null
174                         },
175                     updateState = { newState ->
176                         coroutineScope.launch(backgroundCoroutineContext) {
177                             connection.updateDeviceSettings(settingId, newState.toParcelable())
178                         }
179                     },
180                 )
181 
182             is MultiTogglePreference ->
183                 DeviceSettingModel.MultiTogglePreference(
184                     cachedDevice = cachedDevice,
185                     id = settingId,
186                     title = pref.title,
187                     toggles = pref.toggleInfos.map { it.toModel() },
188                     isAllowedChangingState = pref.isAllowedChangingState,
189                     isActive = pref.isActive,
190                     state = DeviceSettingStateModel.MultiTogglePreferenceState(pref.state),
191                     updateState = { newState ->
192                         coroutineScope.launch(backgroundCoroutineContext) {
193                             connection.updateDeviceSettings(settingId, newState.toParcelable())
194                         }
195                     },
196                 )
197 
198             is DeviceSettingFooterPreference ->
199                 DeviceSettingModel.FooterPreference(
200                     cachedDevice = cachedDevice,
201                     id = settingId,
202                     footerText = pref.footerText,
203                 )
204 
205             is DeviceSettingHelpPreference ->
206                 DeviceSettingModel.HelpPreference(
207                     cachedDevice = cachedDevice,
208                     id = settingId,
209                     intent = pref.intent,
210                 )
211 
212             else -> DeviceSettingModel.Unknown(cachedDevice, settingId)
213         }
214 
toModelnull215     private fun ToggleInfo.toModel(): ToggleModel =
216         ToggleModel(label, DeviceSettingIcon.BitmapIcon(icon))
217 
218     companion object {
219         private val EXPANDABLE_SETTING_IDS =
220             listOf(
221                 DeviceSettingId.DEVICE_SETTING_ID_EXPANDABLE_1,
222                 DeviceSettingId.DEVICE_SETTING_ID_EXPANDABLE_2,
223             )
224         private const val SETTING_ID_EXPAND_LIMIT = 15
225     }
226 }
227