1 /*
2  * 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.systemui.bluetooth.qsdialog
18 
19 import android.bluetooth.BluetoothAdapter
20 import android.platform.test.annotations.EnableFlags
21 import android.testing.TestableLooper
22 import android.view.View
23 import android.view.View.GONE
24 import android.view.View.VISIBLE
25 import androidx.test.ext.junit.runners.AndroidJUnit4
26 import androidx.test.filters.SmallTest
27 import com.android.internal.logging.UiEventLogger
28 import com.android.settingslib.bluetooth.CachedBluetoothDevice
29 import com.android.settingslib.bluetooth.LocalBluetoothManager
30 import com.android.settingslib.flags.Flags
31 import com.android.systemui.SysuiTestCase
32 import com.android.systemui.animation.DialogTransitionAnimator
33 import com.android.systemui.animation.Expandable
34 import com.android.systemui.kosmos.testDispatcher
35 import com.android.systemui.kosmos.testScope
36 import com.android.systemui.plugins.ActivityStarter
37 import com.android.systemui.statusbar.phone.SystemUIDialog
38 import com.android.systemui.testKosmos
39 import com.android.systemui.util.FakeSharedPreferences
40 import com.android.systemui.util.concurrency.FakeExecutor
41 import com.android.systemui.util.kotlin.getMutableStateFlow
42 import com.android.systemui.util.mockito.any
43 import com.android.systemui.util.mockito.nullable
44 import com.android.systemui.util.mockito.whenever
45 import com.android.systemui.util.time.FakeSystemClock
46 import com.google.common.truth.Truth.assertThat
47 import kotlinx.coroutines.CoroutineDispatcher
48 import kotlinx.coroutines.ExperimentalCoroutinesApi
49 import kotlinx.coroutines.flow.MutableSharedFlow
50 import kotlinx.coroutines.flow.MutableStateFlow
51 import kotlinx.coroutines.flow.asStateFlow
52 import kotlinx.coroutines.test.TestScope
53 import kotlinx.coroutines.test.runCurrent
54 import kotlinx.coroutines.test.runTest
55 import org.junit.Before
56 import org.junit.Rule
57 import org.junit.Test
58 import org.junit.runner.RunWith
59 import org.mockito.ArgumentMatchers.anyInt
60 import org.mockito.Mock
61 import org.mockito.Mockito.anyBoolean
62 import org.mockito.Mockito.never
63 import org.mockito.Mockito.verify
64 import org.mockito.junit.MockitoJUnit
65 import org.mockito.junit.MockitoRule
66 
67 @SmallTest
68 @RunWith(AndroidJUnit4::class)
69 @TestableLooper.RunWithLooper(setAsMainLooper = true)
70 @OptIn(ExperimentalCoroutinesApi::class)
71 @EnableFlags(Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE)
72 class BluetoothTileDialogViewModelTest : SysuiTestCase() {
73 
74     @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
75     private val kosmos = testKosmos()
76     private val fakeSystemClock = FakeSystemClock()
77     private val backgroundExecutor = FakeExecutor(fakeSystemClock)
78 
79     private lateinit var bluetoothTileDialogViewModel: BluetoothTileDialogViewModel
80 
81     @Mock private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
82 
83     @Mock private lateinit var bluetoothDeviceMetadataInteractor: BluetoothDeviceMetadataInteractor
84 
85     @Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
86 
87     @Mock private lateinit var deviceItemActionInteractor: DeviceItemActionInteractor
88 
89     @Mock private lateinit var activityStarter: ActivityStarter
90 
91     @Mock private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator
92 
93     @Mock private lateinit var cachedBluetoothDevice: CachedBluetoothDevice
94 
95     @Mock private lateinit var deviceItem: DeviceItem
96 
97     @Mock private lateinit var uiEventLogger: UiEventLogger
98 
99     @Mock private lateinit var bluetoothAdapter: BluetoothAdapter
100 
101     @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
102 
103     @Mock private lateinit var bluetoothTileDialogLogger: BluetoothTileDialogLogger
104 
105     @Mock
106     private lateinit var mBluetoothTileDialogDelegateDelegateFactory:
107         BluetoothTileDialogDelegate.Factory
108 
109     @Mock private lateinit var bluetoothTileDialogDelegate: BluetoothTileDialogDelegate
110 
111     @Mock private lateinit var sysuiDialog: SystemUIDialog
112     @Mock private lateinit var expandable: Expandable
113     @Mock private lateinit var controller: DialogTransitionAnimator.Controller
114 
115     private val sharedPreferences = FakeSharedPreferences()
116 
117     private lateinit var dispatcher: CoroutineDispatcher
118     private lateinit var testScope: TestScope
119 
120     @Before
setUpnull121     fun setUp() {
122         dispatcher = kosmos.testDispatcher
123         testScope = kosmos.testScope
124         // TODO(b/364515243): use real object instead of mock
125         whenever(kosmos.deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow())
126         bluetoothTileDialogViewModel =
127             BluetoothTileDialogViewModel(
128                 deviceItemInteractor,
129                 deviceItemActionInteractor,
130                 BluetoothStateInteractor(
131                     localBluetoothManager,
132                     bluetoothTileDialogLogger,
133                     testScope.backgroundScope,
134                     dispatcher
135                 ),
136                 // TODO(b/316822488): Create FakeBluetoothAutoOnInteractor.
137                 BluetoothAutoOnInteractor(
138                     BluetoothAutoOnRepository(
139                         localBluetoothManager,
140                         bluetoothAdapter,
141                         testScope.backgroundScope,
142                         dispatcher
143                     )
144                 ),
145                 kosmos.audioSharingInteractor,
146                 kosmos.audioSharingButtonViewModelFactory,
147                 bluetoothDeviceMetadataInteractor,
148                 mDialogTransitionAnimator,
149                 activityStarter,
150                 uiEventLogger,
151                 bluetoothTileDialogLogger,
152                 testScope.backgroundScope,
153                 dispatcher,
154                 dispatcher,
155                 sharedPreferences,
156                 mBluetoothTileDialogDelegateDelegateFactory
157             )
158         whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow())
159         whenever(deviceItemInteractor.deviceItemUpdateRequest)
160             .thenReturn(MutableStateFlow(Unit).asStateFlow())
161         whenever(deviceItemInteractor.showSeeAllUpdate).thenReturn(getMutableStateFlow(false))
162         whenever(bluetoothDeviceMetadataInteractor.metadataUpdate).thenReturn(MutableSharedFlow())
163         whenever(mBluetoothTileDialogDelegateDelegateFactory.create(any(), anyInt(), any(), any()))
164             .thenReturn(bluetoothTileDialogDelegate)
165         whenever(bluetoothTileDialogDelegate.createDialog()).thenReturn(sysuiDialog)
166         whenever(sysuiDialog.context).thenReturn(mContext)
167         whenever(bluetoothTileDialogDelegate.bluetoothStateToggle)
168             .thenReturn(getMutableStateFlow(false))
169         whenever(bluetoothTileDialogDelegate.deviceItemClick).thenReturn(MutableSharedFlow())
170         whenever(bluetoothTileDialogDelegate.contentHeight).thenReturn(getMutableStateFlow(0))
171         whenever(bluetoothTileDialogDelegate.bluetoothAutoOnToggle)
172             .thenReturn(getMutableStateFlow(false))
173         whenever(expandable.dialogTransitionController(any())).thenReturn(controller)
174     }
175 
176     @Test
testShowDialog_noAnimationnull177     fun testShowDialog_noAnimation() {
178         testScope.runTest {
179             bluetoothTileDialogViewModel.showDialog(null)
180             runCurrent()
181 
182             verify(mDialogTransitionAnimator, never()).show(any(), any(), any())
183         }
184     }
185 
186     @Test
testShowDialog_animatednull187     fun testShowDialog_animated() {
188         testScope.runTest {
189             bluetoothTileDialogViewModel.showDialog(expandable)
190             runCurrent()
191 
192             verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean())
193         }
194     }
195 
196     @Test
testShowDialog_animated_callInBackgroundThreadnull197     fun testShowDialog_animated_callInBackgroundThread() {
198         testScope.runTest {
199             backgroundExecutor.execute {
200                 bluetoothTileDialogViewModel.showDialog(expandable)
201                 runCurrent()
202 
203                 verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean())
204             }
205         }
206     }
207 
208     @Test
testShowDialog_fetchDeviceItemnull209     fun testShowDialog_fetchDeviceItem() {
210         testScope.runTest {
211             bluetoothTileDialogViewModel.showDialog(null)
212             runCurrent()
213 
214             verify(deviceItemInteractor).deviceItemUpdate
215         }
216     }
217 
218     @Test
testStartSettingsActivity_activityLaunched_dialogDismissednull219     fun testStartSettingsActivity_activityLaunched_dialogDismissed() {
220         testScope.runTest {
221             whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
222             bluetoothTileDialogViewModel.showDialog(null)
223             runCurrent()
224 
225             val clickedView = View(context)
226             bluetoothTileDialogViewModel.onPairNewDeviceClicked(clickedView)
227 
228             verify(uiEventLogger).log(BluetoothTileDialogUiEvent.PAIR_NEW_DEVICE_CLICKED)
229             verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt(), nullable())
230         }
231     }
232 
233     @Test
testBuildUiProperties_bluetoothOn_shouldHideAutoOnnull234     fun testBuildUiProperties_bluetoothOn_shouldHideAutoOn() {
235         testScope.runTest {
236             val actual =
237                 BluetoothTileDialogViewModel.UiProperties.build(
238                     isBluetoothEnabled = true,
239                     isAutoOnToggleFeatureAvailable = true
240                 )
241             assertThat(actual.autoOnToggleVisibility).isEqualTo(GONE)
242         }
243     }
244 
245     @Test
testBuildUiProperties_bluetoothOff_shouldShowAutoOnnull246     fun testBuildUiProperties_bluetoothOff_shouldShowAutoOn() {
247         testScope.runTest {
248             val actual =
249                 BluetoothTileDialogViewModel.UiProperties.build(
250                     isBluetoothEnabled = false,
251                     isAutoOnToggleFeatureAvailable = true
252                 )
253             assertThat(actual.autoOnToggleVisibility).isEqualTo(VISIBLE)
254         }
255     }
256 
257     @Test
testBuildUiProperties_bluetoothOff_autoOnFeatureUnavailable_shouldHideAutoOnnull258     fun testBuildUiProperties_bluetoothOff_autoOnFeatureUnavailable_shouldHideAutoOn() {
259         testScope.runTest {
260             val actual =
261                 BluetoothTileDialogViewModel.UiProperties.build(
262                     isBluetoothEnabled = false,
263                     isAutoOnToggleFeatureAvailable = false
264                 )
265             assertThat(actual.autoOnToggleVisibility).isEqualTo(GONE)
266         }
267     }
268 
269     @Test
testIsAutoOnToggleFeatureAvailable_returnTruenull270     fun testIsAutoOnToggleFeatureAvailable_returnTrue() {
271         testScope.runTest {
272             whenever(bluetoothAdapter.isAutoOnSupported).thenReturn(true)
273 
274             val actual = bluetoothTileDialogViewModel.isAutoOnToggleFeatureAvailable()
275             assertThat(actual).isTrue()
276         }
277     }
278 
279     @Test
testIsAutoOnToggleFeatureAvailable_returnFalsenull280     fun testIsAutoOnToggleFeatureAvailable_returnFalse() {
281         testScope.runTest {
282             whenever(bluetoothAdapter.isAutoOnSupported).thenReturn(false)
283 
284             val actual = bluetoothTileDialogViewModel.isAutoOnToggleFeatureAvailable()
285             assertThat(actual).isFalse()
286         }
287     }
288 }
289