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