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