• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2023 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
18 
19 import android.bluetooth.BluetoothAdapter
20 import android.bluetooth.BluetoothDevice
21 import android.bluetooth.BluetoothUuid
22 import android.bluetooth.le.BluetoothLeScanner
23 import android.bluetooth.le.ScanCallback
24 import android.bluetooth.le.ScanFilter
25 import android.content.Context
26 import android.content.res.Resources
27 import androidx.preference.Preference
28 import com.android.settings.R
29 import com.android.settings.testutils.shadow.ShadowBluetoothAdapter
30 import com.android.settingslib.bluetooth.BluetoothDeviceFilter
31 import com.android.settingslib.bluetooth.CachedBluetoothDevice
32 import com.android.settingslib.bluetooth.CachedBluetoothDeviceManager
33 import com.google.common.truth.Truth.assertThat
34 import kotlinx.coroutines.delay
35 import kotlinx.coroutines.runBlocking
36 import org.junit.Before
37 import org.junit.Rule
38 import org.junit.Test
39 import org.junit.runner.RunWith
40 import org.mockito.ArgumentMatchers.any
41 import org.mockito.ArgumentMatchers.eq
42 import org.mockito.Mock
43 import org.mockito.Mockito.doNothing
44 import org.mockito.Mockito.doReturn
45 import org.mockito.Mockito.mock
46 import org.mockito.Mockito.never
47 import org.mockito.Mockito.spy
48 import org.mockito.Mockito.times
49 import org.mockito.Mockito.verify
50 import org.mockito.Spy
51 import org.mockito.junit.MockitoJUnit
52 import org.mockito.junit.MockitoRule
53 import org.robolectric.RobolectricTestRunner
54 import org.robolectric.RuntimeEnvironment
55 import org.robolectric.annotation.Config
56 import org.mockito.Mockito.`when` as whenever
57 
58 @RunWith(RobolectricTestRunner::class)
59 @Config(shadows = [ShadowBluetoothAdapter::class])
60 class DeviceListPreferenceFragmentTest {
61     @get:Rule
62     val mockito: MockitoRule = MockitoJUnit.rule()
63 
64     @Mock
65     private lateinit var resource: Resources
66 
67     @Mock
68     private lateinit var context: Context
69 
70     @Mock
71     private lateinit var bluetoothLeScanner: BluetoothLeScanner
72 
73     @Mock
74     private lateinit var cachedDeviceManager: CachedBluetoothDeviceManager
75 
76     @Mock
77     private lateinit var cachedDevice: CachedBluetoothDevice
78 
79     @Spy
80     private var fragment = TestFragment()
81 
82     private lateinit var myDevicePreference: Preference
83     private lateinit var bluetoothAdapter: BluetoothAdapter
84 
85     @Before
setUpnull86     fun setUp() {
87         doReturn(context).`when`(fragment).context
88         doReturn(resource).`when`(fragment).resources
89         doNothing().`when`(fragment).onDeviceAdded(cachedDevice)
90         bluetoothAdapter = spy(BluetoothAdapter.getDefaultAdapter())
91         fragment.mBluetoothAdapter = bluetoothAdapter
92         fragment.mCachedDeviceManager = cachedDeviceManager
93 
94         myDevicePreference = Preference(RuntimeEnvironment.application)
95     }
96 
97     @Test
setUpdateMyDevicePreference_setTitleCorrectlynull98     fun setUpdateMyDevicePreference_setTitleCorrectly() {
99         doReturn(FOOTAGE_MAC_STRING).`when`(fragment)
100             .getString(eq(R.string.bluetooth_footer_mac_message), any())
101 
102         fragment.updateFooterPreference(myDevicePreference)
103 
104         assertThat(myDevicePreference.title).isEqualTo(FOOTAGE_MAC_STRING)
105     }
106 
107     @Test
testEnableDisableScanning_testStateAfterEnableDisablenull108     fun testEnableDisableScanning_testStateAfterEnableDisable() {
109         fragment.enableScanning()
110         verify(fragment).startScanning()
111         assertThat(fragment.mScanEnabled).isTrue()
112 
113         fragment.disableScanning()
114         verify(fragment).stopScanning()
115         assertThat(fragment.mScanEnabled).isFalse()
116     }
117 
118     @Test
testScanningStateChanged_testScanStartednull119     fun testScanningStateChanged_testScanStarted() {
120         fragment.enableScanning()
121         assertThat(fragment.mScanEnabled).isTrue()
122         verify(fragment).startScanning()
123 
124         fragment.onScanningStateChanged(true)
125         verify(fragment, times(1)).startScanning()
126     }
127 
128     @Test
testScanningStateChanged_testScanFinishednull129     fun testScanningStateChanged_testScanFinished() {
130         // Could happen when last scanning not done while current scan gets enabled
131         fragment.enableScanning()
132         verify(fragment).startScanning()
133         assertThat(fragment.mScanEnabled).isTrue()
134 
135         fragment.onScanningStateChanged(false)
136         verify(fragment, times(2)).startScanning()
137     }
138 
139     @Test
testScanningStateChanged_testScanStateMultiplenull140     fun testScanningStateChanged_testScanStateMultiple() {
141         // Could happen when last scanning not done while current scan gets enabled
142         fragment.enableScanning()
143         assertThat(fragment.mScanEnabled).isTrue()
144         verify(fragment).startScanning()
145 
146         fragment.onScanningStateChanged(true)
147         verify(fragment, times(1)).startScanning()
148 
149         fragment.onScanningStateChanged(false)
150         verify(fragment, times(2)).startScanning()
151 
152         fragment.onScanningStateChanged(true)
153         verify(fragment, times(2)).startScanning()
154 
155         fragment.disableScanning()
156         verify(fragment).stopScanning()
157 
158         fragment.onScanningStateChanged(false)
159         verify(fragment, times(2)).startScanning()
160 
161         fragment.onScanningStateChanged(true)
162         verify(fragment, times(2)).startScanning()
163     }
164 
165     @Test
testScanningStateChanged_testScanFinishedAfterDisablenull166     fun testScanningStateChanged_testScanFinishedAfterDisable() {
167         fragment.enableScanning()
168         verify(fragment).startScanning()
169         assertThat(fragment.mScanEnabled).isTrue()
170 
171         fragment.disableScanning()
172         verify(fragment).stopScanning()
173         assertThat(fragment.mScanEnabled).isFalse()
174 
175         fragment.onScanningStateChanged(false)
176         verify(fragment, times(1)).startScanning()
177     }
178 
179     @Test
testScanningStateChanged_testScanStartedAfterDisablenull180     fun testScanningStateChanged_testScanStartedAfterDisable() {
181         fragment.enableScanning()
182         verify(fragment).startScanning()
183         assertThat(fragment.mScanEnabled).isTrue()
184 
185         fragment.disableScanning()
186         verify(fragment).stopScanning()
187         assertThat(fragment.mScanEnabled).isFalse()
188 
189         fragment.onScanningStateChanged(true)
190         verify(fragment, times(1)).startScanning()
191     }
192 
193     @Test
startScanning_setLeScanFilter_shouldStartLeScannull194     fun startScanning_setLeScanFilter_shouldStartLeScan() {
195         val leScanFilter = ScanFilter.Builder()
196             .setServiceData(BluetoothUuid.HEARING_AID, byteArrayOf(0), byteArrayOf(0))
197             .build()
198         doReturn(bluetoothLeScanner).`when`(bluetoothAdapter).bluetoothLeScanner
199 
200         fragment.setFilter(listOf(leScanFilter))
201         fragment.startScanning()
202 
203         verify(bluetoothLeScanner).startScan(eq(listOf(leScanFilter)), any(), any<ScanCallback>())
204     }
205 
206     @Test
<lambda>null207     fun addCachedDevices_whenFilterIsNull_onDeviceAddedIsCalled() = runBlocking {
208         val mockCachedDevice = mock(CachedBluetoothDevice::class.java)
209         whenever(cachedDeviceManager.cachedDevicesCopy).thenReturn(listOf(mockCachedDevice))
210         fragment.lifecycleScope = this
211 
212         fragment.addCachedDevices(filterForCachedDevices = null)
213         delay(100)
214 
215         verify(fragment).onDeviceAdded(mockCachedDevice)
216     }
217 
218     @Test
<lambda>null219     fun addCachedDevices_whenFilterMatched_onDeviceAddedIsCalled() = runBlocking {
220         val mockBluetoothDevice = mock(BluetoothDevice::class.java)
221         whenever(mockBluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_NONE)
222         whenever(cachedDevice.device).thenReturn(mockBluetoothDevice)
223         whenever(cachedDeviceManager.cachedDevicesCopy).thenReturn(listOf(cachedDevice))
224         fragment.lifecycleScope = this
225 
226         fragment.addCachedDevices(BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER)
227         delay(100)
228 
229         verify(fragment).onDeviceAdded(cachedDevice)
230     }
231 
232     @Test
<lambda>null233     fun addCachedDevices_whenFilterNoMatch_onDeviceAddedNotCalled() = runBlocking {
234         val mockBluetoothDevice = mock(BluetoothDevice::class.java)
235         whenever(mockBluetoothDevice.bondState).thenReturn(BluetoothDevice.BOND_BONDED)
236         whenever(cachedDevice.device).thenReturn(mockBluetoothDevice)
237         whenever(cachedDeviceManager.cachedDevicesCopy).thenReturn(listOf(cachedDevice))
238         fragment.lifecycleScope = this
239 
240         fragment.addCachedDevices(BluetoothDeviceFilter.UNBONDED_DEVICE_FILTER)
241         delay(100)
242 
243         verify(fragment, never()).onDeviceAdded(cachedDevice)
244     }
245 
246     /**
247      * Fragment to test since `DeviceListPreferenceFragment` is abstract
248      */
249     open class TestFragment : DeviceListPreferenceFragment(null) {
getMetricsCategorynull250         override fun getMetricsCategory() = 0
251         override fun initPreferencesFromPreferenceScreen() {}
252         override val deviceListKey = "device_list"
getLogTagnull253         override fun getLogTag() = null
254         override fun getPreferenceScreenResId() = 0
255     }
256 
257     private companion object {
258         const val FOOTAGE_MAC_STRING = "Bluetooth mac: xxxx"
259     }
260 }
261