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