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.plugins.ActivityStarter
35 import com.android.systemui.statusbar.phone.SystemUIDialog
36 import com.android.systemui.util.FakeSharedPreferences
37 import com.android.systemui.util.concurrency.FakeExecutor
38 import com.android.systemui.util.kotlin.getMutableStateFlow
39 import com.android.systemui.util.mockito.any
40 import com.android.systemui.util.mockito.nullable
41 import com.android.systemui.util.mockito.whenever
42 import com.android.systemui.util.time.FakeSystemClock
43 import com.google.common.truth.Truth.assertThat
44 import kotlinx.coroutines.CoroutineDispatcher
45 import kotlinx.coroutines.flow.MutableSharedFlow
46 import kotlinx.coroutines.flow.MutableStateFlow
47 import kotlinx.coroutines.flow.asStateFlow
48 import kotlinx.coroutines.test.TestCoroutineScheduler
49 import kotlinx.coroutines.test.TestScope
50 import kotlinx.coroutines.test.UnconfinedTestDispatcher
51 import kotlinx.coroutines.test.runTest
52 import org.junit.Before
53 import org.junit.Rule
54 import org.junit.Test
55 import org.junit.runner.RunWith
56 import org.mockito.ArgumentMatchers.anyInt
57 import org.mockito.Mock
58 import org.mockito.Mockito.anyBoolean
59 import org.mockito.Mockito.never
60 import org.mockito.Mockito.verify
61 import org.mockito.junit.MockitoJUnit
62 import org.mockito.junit.MockitoRule
63 
64 @SmallTest
65 @RunWith(AndroidJUnit4::class)
66 @TestableLooper.RunWithLooper(setAsMainLooper = true)
67 @EnableFlags(Flags.FLAG_BLUETOOTH_QS_TILE_DIALOG_AUTO_ON_TOGGLE)
68 class BluetoothTileDialogViewModelTest : SysuiTestCase() {
69 
70     @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
71     private val fakeSystemClock = FakeSystemClock()
72     private val backgroundExecutor = FakeExecutor(fakeSystemClock)
73 
74     private lateinit var bluetoothTileDialogViewModel: BluetoothTileDialogViewModel
75 
76     @Mock private lateinit var bluetoothStateInteractor: BluetoothStateInteractor
77 
78     @Mock private lateinit var audioSharingInteractor: AudioSharingInteractor
79 
80     @Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
81 
82     @Mock private lateinit var deviceItemActionInteractor: DeviceItemActionInteractor
83 
84     @Mock private lateinit var activityStarter: ActivityStarter
85 
86     @Mock private lateinit var mDialogTransitionAnimator: DialogTransitionAnimator
87 
88     @Mock private lateinit var cachedBluetoothDevice: CachedBluetoothDevice
89 
90     @Mock private lateinit var deviceItem: DeviceItem
91 
92     @Mock private lateinit var uiEventLogger: UiEventLogger
93 
94     @Mock private lateinit var bluetoothAdapter: BluetoothAdapter
95 
96     @Mock private lateinit var localBluetoothManager: LocalBluetoothManager
97 
98     @Mock private lateinit var bluetoothTileDialogLogger: BluetoothTileDialogLogger
99 
100     @Mock
101     private lateinit var mBluetoothTileDialogDelegateDelegateFactory:
102         BluetoothTileDialogDelegate.Factory
103 
104     @Mock private lateinit var bluetoothTileDialogDelegate: BluetoothTileDialogDelegate
105 
106     @Mock private lateinit var sysuiDialog: SystemUIDialog
107     @Mock private lateinit var expandable: Expandable
108     @Mock private lateinit var controller: DialogTransitionAnimator.Controller
109 
110     private val sharedPreferences = FakeSharedPreferences()
111 
112     private lateinit var scheduler: TestCoroutineScheduler
113     private lateinit var dispatcher: CoroutineDispatcher
114     private lateinit var testScope: TestScope
115 
116     @Before
setUpnull117     fun setUp() {
118         scheduler = TestCoroutineScheduler()
119         dispatcher = UnconfinedTestDispatcher(scheduler)
120         testScope = TestScope(dispatcher)
121         bluetoothTileDialogViewModel =
122             BluetoothTileDialogViewModel(
123                 deviceItemInteractor,
124                 deviceItemActionInteractor,
125                 BluetoothStateInteractor(
126                     localBluetoothManager,
127                     bluetoothTileDialogLogger,
128                     testScope.backgroundScope,
129                     dispatcher
130                 ),
131                 // TODO(b/316822488): Create FakeBluetoothAutoOnInteractor.
132                 BluetoothAutoOnInteractor(
133                     BluetoothAutoOnRepository(
134                         localBluetoothManager,
135                         bluetoothAdapter,
136                         testScope.backgroundScope,
137                         dispatcher
138                     )
139                 ),
140                 audioSharingInteractor,
141                 mDialogTransitionAnimator,
142                 activityStarter,
143                 uiEventLogger,
144                 testScope.backgroundScope,
145                 dispatcher,
146                 dispatcher,
147                 sharedPreferences,
148                 mBluetoothTileDialogDelegateDelegateFactory
149             )
150         whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow())
151         whenever(deviceItemInteractor.deviceItemUpdateRequest)
152             .thenReturn(MutableStateFlow(Unit).asStateFlow())
153         whenever(mBluetoothTileDialogDelegateDelegateFactory.create(any(), anyInt(), any(), any()))
154             .thenReturn(bluetoothTileDialogDelegate)
155         whenever(bluetoothTileDialogDelegate.createDialog()).thenReturn(sysuiDialog)
156         whenever(sysuiDialog.context).thenReturn(mContext)
157         whenever(bluetoothTileDialogDelegate.bluetoothStateToggle)
158             .thenReturn(getMutableStateFlow(false))
159         whenever(bluetoothTileDialogDelegate.deviceItemClick)
160             .thenReturn(getMutableStateFlow(deviceItem))
161         whenever(bluetoothTileDialogDelegate.contentHeight).thenReturn(getMutableStateFlow(0))
162         whenever(bluetoothTileDialogDelegate.bluetoothAutoOnToggle)
163             .thenReturn(getMutableStateFlow(false))
164         whenever(audioSharingInteractor.audioSharingButtonStateUpdate)
165             .thenReturn(getMutableStateFlow(AudioSharingButtonState.Gone))
166         whenever(expandable.dialogTransitionController(any())).thenReturn(controller)
167     }
168 
169     @Test
testShowDialog_noAnimationnull170     fun testShowDialog_noAnimation() {
171         testScope.runTest {
172             bluetoothTileDialogViewModel.showDialog(null)
173 
174             verify(mDialogTransitionAnimator, never()).show(any(), any(), any())
175         }
176     }
177 
178     @Test
testShowDialog_animatednull179     fun testShowDialog_animated() {
180         testScope.runTest {
181             bluetoothTileDialogViewModel.showDialog(expandable)
182 
183             verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean())
184         }
185     }
186 
187     @Test
testShowDialog_animated_callInBackgroundThreadnull188     fun testShowDialog_animated_callInBackgroundThread() {
189         testScope.runTest {
190             backgroundExecutor.execute {
191                 bluetoothTileDialogViewModel.showDialog(expandable)
192 
193                 verify(mDialogTransitionAnimator).show(any(), any(), anyBoolean())
194             }
195         }
196     }
197 
198     @Test
testShowDialog_fetchDeviceItemnull199     fun testShowDialog_fetchDeviceItem() {
200         testScope.runTest {
201             bluetoothTileDialogViewModel.showDialog(null)
202 
203             verify(deviceItemInteractor).deviceItemUpdate
204         }
205     }
206 
207     @Test
testStartSettingsActivity_activityLaunched_dialogDismissednull208     fun testStartSettingsActivity_activityLaunched_dialogDismissed() {
209         testScope.runTest {
210             whenever(deviceItem.cachedBluetoothDevice).thenReturn(cachedBluetoothDevice)
211             bluetoothTileDialogViewModel.showDialog(null)
212 
213             val clickedView = View(context)
214             bluetoothTileDialogViewModel.onPairNewDeviceClicked(clickedView)
215 
216             verify(uiEventLogger).log(BluetoothTileDialogUiEvent.PAIR_NEW_DEVICE_CLICKED)
217             verify(activityStarter).postStartActivityDismissingKeyguard(any(), anyInt(), nullable())
218         }
219     }
220 
221     @Test
testBuildUiProperties_bluetoothOn_shouldHideAutoOnnull222     fun testBuildUiProperties_bluetoothOn_shouldHideAutoOn() {
223         testScope.runTest {
224             val actual =
225                 BluetoothTileDialogViewModel.UiProperties.build(
226                     isBluetoothEnabled = true,
227                     isAutoOnToggleFeatureAvailable = true
228                 )
229             assertThat(actual.autoOnToggleVisibility).isEqualTo(GONE)
230         }
231     }
232 
233     @Test
testBuildUiProperties_bluetoothOff_shouldShowAutoOnnull234     fun testBuildUiProperties_bluetoothOff_shouldShowAutoOn() {
235         testScope.runTest {
236             val actual =
237                 BluetoothTileDialogViewModel.UiProperties.build(
238                     isBluetoothEnabled = false,
239                     isAutoOnToggleFeatureAvailable = true
240                 )
241             assertThat(actual.autoOnToggleVisibility).isEqualTo(VISIBLE)
242         }
243     }
244 
245     @Test
testBuildUiProperties_bluetoothOff_autoOnFeatureUnavailable_shouldHideAutoOnnull246     fun testBuildUiProperties_bluetoothOff_autoOnFeatureUnavailable_shouldHideAutoOn() {
247         testScope.runTest {
248             val actual =
249                 BluetoothTileDialogViewModel.UiProperties.build(
250                     isBluetoothEnabled = false,
251                     isAutoOnToggleFeatureAvailable = false
252                 )
253             assertThat(actual.autoOnToggleVisibility).isEqualTo(GONE)
254         }
255     }
256 
257     @Test
testIsAutoOnToggleFeatureAvailable_returnTruenull258     fun testIsAutoOnToggleFeatureAvailable_returnTrue() {
259         testScope.runTest {
260             whenever(bluetoothAdapter.isAutoOnSupported).thenReturn(true)
261 
262             val actual = bluetoothTileDialogViewModel.isAutoOnToggleFeatureAvailable()
263             assertThat(actual).isTrue()
264         }
265     }
266 
267     @Test
testIsAutoOnToggleFeatureAvailable_returnFalsenull268     fun testIsAutoOnToggleFeatureAvailable_returnFalse() {
269         testScope.runTest {
270             whenever(bluetoothAdapter.isAutoOnSupported).thenReturn(false)
271 
272             val actual = bluetoothTileDialogViewModel.isAutoOnToggleFeatureAvailable()
273             assertThat(actual).isFalse()
274         }
275     }
276 }
277