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.settings.bluetooth.ui.viewmodel 18 19 import android.app.Application 20 import android.bluetooth.BluetoothAdapter 21 import android.graphics.Bitmap 22 import androidx.test.core.app.ApplicationProvider 23 import com.android.settings.bluetooth.ui.layout.DeviceSettingLayout 24 import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel 25 import com.android.settings.bluetooth.ui.model.FragmentTypeModel 26 import com.android.settings.testutils.FakeFeatureFactory 27 import com.android.settingslib.bluetooth.CachedBluetoothDevice 28 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId 29 import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository 30 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel 31 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel 32 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon 33 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel 34 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel 35 import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel 36 import com.google.common.truth.Truth.assertThat 37 import kotlinx.coroutines.ExperimentalCoroutinesApi 38 import kotlinx.coroutines.flow.flowOf 39 import kotlinx.coroutines.flow.launchIn 40 import kotlinx.coroutines.flow.onEach 41 import kotlinx.coroutines.test.TestScope 42 import kotlinx.coroutines.test.runCurrent 43 import kotlinx.coroutines.test.runTest 44 import org.junit.Before 45 import org.junit.Rule 46 import org.junit.Test 47 import org.junit.runner.RunWith 48 import org.mockito.ArgumentMatchers.eq 49 import org.mockito.Mock 50 import org.mockito.Mockito.any 51 import org.mockito.Mockito.times 52 import org.mockito.Mockito.verify 53 import org.mockito.Mockito.`when` 54 import org.mockito.junit.MockitoJUnit 55 import org.mockito.junit.MockitoRule 56 import org.robolectric.RobolectricTestRunner 57 58 @OptIn(ExperimentalCoroutinesApi::class) 59 @RunWith(RobolectricTestRunner::class) 60 class BluetoothDeviceDetailsViewModelTest { 61 @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule() 62 63 @Mock private lateinit var cachedDevice: CachedBluetoothDevice 64 65 @Mock private lateinit var bluetoothAdapter: BluetoothAdapter 66 67 @Mock private lateinit var repository: DeviceSettingRepository 68 69 private lateinit var underTest: BluetoothDeviceDetailsViewModel 70 private lateinit var featureFactory: FakeFeatureFactory 71 private val testScope = TestScope() 72 73 @Before 74 fun setUp() { 75 val application = ApplicationProvider.getApplicationContext<Application>() 76 featureFactory = FakeFeatureFactory.setupForTest() 77 78 `when`( 79 featureFactory.bluetoothFeatureProvider.getDeviceSettingRepository( 80 eq(application), eq(bluetoothAdapter), any() 81 )) 82 .thenReturn(repository) 83 84 underTest = 85 BluetoothDeviceDetailsViewModel( 86 application, 87 bluetoothAdapter, 88 cachedDevice, 89 testScope.testScheduler) 90 } 91 92 @Test 93 fun getItems_returnConfigMainMainItems() { 94 testScope.runTest { 95 `when`(repository.getDeviceSettingsConfig(cachedDevice)) 96 .thenReturn( 97 DeviceSettingConfigModel( 98 listOf(BUILTIN_SETTING_ITEM_1, BUILDIN_SETTING_ITEM_2), listOf(), null)) 99 100 val keys = underTest.getItems(FragmentTypeModel.DeviceDetailsMainFragment) 101 102 assertThat(keys).containsExactly(BUILTIN_SETTING_ITEM_1, BUILDIN_SETTING_ITEM_2) 103 } 104 } 105 106 @Test 107 fun getHelpItems_mainPage_returnNull() { 108 testScope.runTest { 109 `when`(repository.getDeviceSettingsConfig(cachedDevice)) 110 .thenReturn( 111 DeviceSettingConfigModel( 112 listOf(BUILTIN_SETTING_ITEM_1, BUILDIN_SETTING_ITEM_2), 113 listOf(), 114 SETTING_ITEM_HELP)) 115 116 val item = underTest.getHelpItem(FragmentTypeModel.DeviceDetailsMainFragment) 117 118 assertThat(item).isNull() 119 } 120 } 121 122 @Test 123 fun getHelpItems_moreSettings_returnConfigHelpItem() { 124 testScope.runTest { 125 `when`(repository.getDeviceSettingsConfig(cachedDevice)) 126 .thenReturn( 127 DeviceSettingConfigModel( 128 listOf(BUILTIN_SETTING_ITEM_1, BUILDIN_SETTING_ITEM_2), 129 listOf(), 130 SETTING_ITEM_HELP)) 131 132 val item = underTest.getHelpItem(FragmentTypeModel.DeviceDetailsMoreSettingsFragment) 133 134 assertThat(item).isSameInstanceAs(SETTING_ITEM_HELP) 135 } 136 } 137 138 @Test 139 fun getDeviceSetting_returnRepositoryResponse() { 140 testScope.runTest { 141 val remoteSettingId1 = 10001 142 val pref = buildMultiTogglePreference(remoteSettingId1) 143 `when`(repository.getDeviceSettingsConfig(cachedDevice)) 144 .thenReturn( 145 DeviceSettingConfigModel( 146 listOf( 147 BUILTIN_SETTING_ITEM_1, 148 buildRemoteSettingItem(remoteSettingId1), 149 ), 150 listOf(), 151 null)) 152 `when`(repository.getDeviceSetting(cachedDevice, remoteSettingId1)) 153 .thenReturn(flowOf(pref)) 154 155 var deviceSettingPreference: DeviceSettingPreferenceModel? = null 156 underTest 157 .getDeviceSetting(cachedDevice, remoteSettingId1) 158 .onEach { deviceSettingPreference = it } 159 .launchIn(testScope.backgroundScope) 160 runCurrent() 161 162 assertThat(deviceSettingPreference?.id).isEqualTo(pref.id) 163 verify(repository, times(1)).getDeviceSetting(cachedDevice, remoteSettingId1) 164 } 165 } 166 167 @Test 168 fun getLayout_builtinDeviceSettings() { 169 testScope.runTest { 170 `when`(repository.getDeviceSettingsConfig(cachedDevice)) 171 .thenReturn( 172 DeviceSettingConfigModel( 173 listOf(BUILTIN_SETTING_ITEM_1, BUILDIN_SETTING_ITEM_2), listOf(), null)) 174 175 val layout = underTest.getLayout(FragmentTypeModel.DeviceDetailsMainFragment)!! 176 177 assertThat(getLatestLayout(layout)) 178 .isEqualTo( 179 listOf( 180 listOf(DeviceSettingId.DEVICE_SETTING_ID_HEADER), 181 listOf(DeviceSettingId.DEVICE_SETTING_ID_ACTION_BUTTONS))) 182 } 183 } 184 185 @Test 186 fun getLayout_remoteDeviceSettings() { 187 val remoteSettingId1 = 10001 188 val remoteSettingId2 = 10002 189 val remoteSettingId3 = 10003 190 testScope.runTest { 191 `when`(repository.getDeviceSettingsConfig(cachedDevice)) 192 .thenReturn( 193 DeviceSettingConfigModel( 194 listOf( 195 BUILTIN_SETTING_ITEM_1, 196 buildRemoteSettingItem(remoteSettingId1), 197 buildRemoteSettingItem(remoteSettingId2), 198 buildRemoteSettingItem(remoteSettingId3), 199 ), 200 listOf(), 201 null)) 202 `when`(repository.getDeviceSetting(cachedDevice, remoteSettingId1)) 203 .thenReturn(flowOf(buildMultiTogglePreference(remoteSettingId1))) 204 `when`(repository.getDeviceSetting(cachedDevice, remoteSettingId2)) 205 .thenReturn(flowOf(buildMultiTogglePreference(remoteSettingId2))) 206 `when`(repository.getDeviceSetting(cachedDevice, remoteSettingId3)) 207 .thenReturn(flowOf(buildActionSwitchPreference(remoteSettingId3))) 208 209 val layout = underTest.getLayout(FragmentTypeModel.DeviceDetailsMainFragment)!! 210 211 assertThat(getLatestLayout(layout)) 212 .isEqualTo( 213 listOf( 214 listOf(DeviceSettingId.DEVICE_SETTING_ID_HEADER), 215 listOf(remoteSettingId1), 216 listOf(remoteSettingId2), 217 listOf(remoteSettingId3), 218 )) 219 } 220 } 221 222 private fun getLatestLayout(layout: DeviceSettingLayout): List<List<Int>> { 223 val latestLayout = MutableList(layout.rows.size) { emptyList<Int>() } 224 for (i in layout.rows.indices) { 225 layout.rows[i] 226 .columns 227 .onEach { latestLayout[i] = it.map { c -> c.settingId } } 228 .launchIn(testScope.backgroundScope) 229 } 230 231 testScope.runCurrent() 232 return latestLayout.filter { !it.isEmpty() }.toList() 233 } 234 235 private fun buildMultiTogglePreference(settingId: Int) = 236 DeviceSettingModel.MultiTogglePreference( 237 cachedDevice, 238 settingId, 239 "title", 240 toggles = 241 listOf( 242 ToggleModel( 243 "toggle1", 244 DeviceSettingIcon.BitmapIcon( 245 Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)))), 246 isActive = true, 247 state = DeviceSettingStateModel.MultiTogglePreferenceState(0), 248 isAllowedChangingState = true, 249 updateState = {}) 250 251 private fun buildActionSwitchPreference(settingId: Int) = 252 DeviceSettingModel.ActionSwitchPreference(cachedDevice, settingId, "title") 253 254 private fun buildRemoteSettingItem(settingId: Int) = 255 DeviceSettingConfigItemModel.AppProvidedItem(settingId, false) 256 257 private companion object { 258 val BUILTIN_SETTING_ITEM_1 = 259 DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem( 260 DeviceSettingId.DEVICE_SETTING_ID_HEADER, false, "bluetooth_device_header") 261 val BUILDIN_SETTING_ITEM_2 = 262 DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem( 263 DeviceSettingId.DEVICE_SETTING_ID_ACTION_BUTTONS, false, "action_buttons") 264 val SETTING_ITEM_HELP = DeviceSettingConfigItemModel.AppProvidedItem(12345, false) 265 } 266 } 267