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