1 /* <lambda>null2 * Copyright (C) 2022 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.statusbar.pipeline.wifi.ui.viewmodel 18 19 import android.content.Context 20 import androidx.annotation.DrawableRes 21 import androidx.test.filters.SmallTest 22 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_CONNECTION_STRENGTH 23 import com.android.settingslib.AccessibilityContentDescriptions.WIFI_NO_CONNECTION 24 import com.android.systemui.SysuiTestCase 25 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription 26 import com.android.systemui.log.table.TableLogBuffer 27 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_FULL_ICONS 28 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_INTERNET_ICONS 29 import com.android.systemui.statusbar.connectivity.WifiIcons.WIFI_NO_NETWORK 30 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository 31 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor 32 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModel 33 import com.android.systemui.statusbar.pipeline.airplane.ui.viewmodel.AirplaneModeViewModelImpl 34 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository 35 import com.android.systemui.statusbar.pipeline.shared.ConnectivityConstants 36 import com.android.systemui.statusbar.pipeline.shared.data.model.ConnectivitySlot 37 import com.android.systemui.statusbar.pipeline.shared.data.repository.FakeConnectivityRepository 38 import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository 39 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor 40 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractorImpl 41 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants 42 import com.android.systemui.statusbar.pipeline.wifi.shared.model.WifiNetworkModel 43 import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon 44 import com.android.systemui.statusbar.pipeline.wifi.ui.model.WifiIcon.Companion.NO_INTERNET 45 import com.google.common.truth.Truth.assertThat 46 import kotlinx.coroutines.CoroutineScope 47 import kotlinx.coroutines.Dispatchers 48 import kotlinx.coroutines.cancel 49 import kotlinx.coroutines.flow.MutableStateFlow 50 import kotlinx.coroutines.flow.launchIn 51 import kotlinx.coroutines.runBlocking 52 import kotlinx.coroutines.yield 53 import org.junit.After 54 import org.junit.Before 55 import org.junit.Test 56 import org.junit.runner.RunWith 57 import org.junit.runners.Parameterized 58 import org.junit.runners.Parameterized.Parameters 59 import org.mockito.Mock 60 import org.mockito.Mockito.`when` as whenever 61 import org.mockito.MockitoAnnotations 62 63 @SmallTest 64 @RunWith(Parameterized::class) 65 internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase) : 66 SysuiTestCase() { 67 68 private lateinit var underTest: WifiViewModel 69 70 @Mock private lateinit var tableLogBuffer: TableLogBuffer 71 @Mock private lateinit var connectivityConstants: ConnectivityConstants 72 @Mock private lateinit var wifiConstants: WifiConstants 73 private lateinit var airplaneModeRepository: FakeAirplaneModeRepository 74 private lateinit var connectivityRepository: FakeConnectivityRepository 75 private lateinit var wifiRepository: FakeWifiRepository 76 private lateinit var interactor: WifiInteractor 77 private lateinit var airplaneModeViewModel: AirplaneModeViewModel 78 private lateinit var scope: CoroutineScope 79 80 @Before 81 fun setUp() { 82 MockitoAnnotations.initMocks(this) 83 airplaneModeRepository = FakeAirplaneModeRepository() 84 connectivityRepository = FakeConnectivityRepository() 85 wifiRepository = FakeWifiRepository() 86 wifiRepository.setIsWifiEnabled(true) 87 scope = CoroutineScope(IMMEDIATE) 88 interactor = WifiInteractorImpl(connectivityRepository, wifiRepository, scope) 89 airplaneModeViewModel = 90 AirplaneModeViewModelImpl( 91 AirplaneModeInteractor( 92 airplaneModeRepository, 93 connectivityRepository, 94 FakeMobileConnectionsRepository(), 95 ), 96 tableLogBuffer, 97 scope, 98 ) 99 } 100 101 @After 102 fun tearDown() { 103 scope.cancel() 104 } 105 106 @Test 107 fun wifiIcon() = 108 runBlocking(IMMEDIATE) { 109 wifiRepository.setIsWifiEnabled(testCase.enabled) 110 wifiRepository.setIsWifiDefault(testCase.isDefault) 111 connectivityRepository.setForceHiddenIcons( 112 if (testCase.forceHidden) { 113 setOf(ConnectivitySlot.WIFI) 114 } else { 115 setOf() 116 } 117 ) 118 whenever(wifiConstants.alwaysShowIconIfEnabled) 119 .thenReturn(testCase.alwaysShowIconWhenEnabled) 120 whenever(connectivityConstants.hasDataCapabilities) 121 .thenReturn(testCase.hasDataCapabilities) 122 underTest = 123 WifiViewModel( 124 airplaneModeViewModel, 125 shouldShowSignalSpacerProvider = { MutableStateFlow(false) }, 126 connectivityConstants, 127 context, 128 tableLogBuffer, 129 interactor, 130 scope, 131 wifiConstants, 132 ) 133 134 val iconFlow = underTest.wifiIcon 135 val job = iconFlow.launchIn(this) 136 137 // WHEN we set a certain network 138 wifiRepository.setWifiNetwork(testCase.network) 139 yield() 140 141 // THEN we get the expected icon 142 val actualIcon = iconFlow.value 143 when (testCase.expected) { 144 null -> { 145 assertThat(actualIcon).isInstanceOf(WifiIcon.Hidden::class.java) 146 } 147 else -> { 148 assertThat(actualIcon).isInstanceOf(WifiIcon.Visible::class.java) 149 val actualIconVisible = actualIcon as WifiIcon.Visible 150 assertThat(actualIconVisible.icon.res).isEqualTo(testCase.expected.iconResource) 151 val expectedContentDescription = 152 testCase.expected.contentDescription.invoke(context) 153 assertThat(actualIconVisible.contentDescription.loadContentDescription(context)) 154 .isEqualTo(expectedContentDescription) 155 } 156 } 157 158 job.cancel() 159 } 160 161 internal data class Expected( 162 /** The resource that should be used for the icon. */ 163 @DrawableRes val iconResource: Int, 164 165 /** A function that, given a context, calculates the correct content description string. */ 166 val contentDescription: (Context) -> String, 167 168 /** A human-readable description used for the test names. */ 169 val description: String, 170 ) { 171 override fun toString() = description 172 } 173 174 // Note: We use default values for the boolean parameters to reflect a "typical configuration" 175 // for wifi. This allows each TestCase to only define the parameter values that are critical 176 // for the test function. 177 internal data class TestCase( 178 val enabled: Boolean = true, 179 val forceHidden: Boolean = false, 180 val alwaysShowIconWhenEnabled: Boolean = false, 181 val hasDataCapabilities: Boolean = true, 182 val isDefault: Boolean = false, 183 val network: WifiNetworkModel, 184 185 /** The expected output. Null if we expect the output to be hidden. */ 186 val expected: Expected? 187 ) { 188 override fun toString(): String { 189 return "when INPUT(enabled=$enabled, " + 190 "forceHidden=$forceHidden, " + 191 "showWhenEnabled=$alwaysShowIconWhenEnabled, " + 192 "hasDataCaps=$hasDataCapabilities, " + 193 "isDefault=$isDefault, " + 194 "network=$network) then " + 195 "EXPECTED($expected)" 196 } 197 } 198 199 companion object { 200 @Parameters(name = "{0}") @JvmStatic fun data(): Collection<TestCase> = testData 201 202 private val testData: List<TestCase> = 203 listOf( 204 // Enabled = false => no networks shown 205 TestCase( 206 enabled = false, 207 network = 208 WifiNetworkModel.CarrierMerged(NETWORK_ID, subscriptionId = 1, level = 1), 209 expected = null, 210 ), 211 TestCase( 212 enabled = false, 213 network = WifiNetworkModel.Inactive, 214 expected = null, 215 ), 216 TestCase( 217 enabled = false, 218 network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 1), 219 expected = null, 220 ), 221 TestCase( 222 enabled = false, 223 network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 3), 224 expected = null, 225 ), 226 227 // forceHidden = true => no networks shown 228 TestCase( 229 forceHidden = true, 230 network = 231 WifiNetworkModel.CarrierMerged(NETWORK_ID, subscriptionId = 1, level = 1), 232 expected = null, 233 ), 234 TestCase( 235 forceHidden = true, 236 network = WifiNetworkModel.Inactive, 237 expected = null, 238 ), 239 TestCase( 240 enabled = false, 241 network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 2), 242 expected = null, 243 ), 244 TestCase( 245 forceHidden = true, 246 network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 1), 247 expected = null, 248 ), 249 250 // alwaysShowIconWhenEnabled = true => all Inactive and Active networks shown 251 TestCase( 252 alwaysShowIconWhenEnabled = true, 253 network = WifiNetworkModel.Inactive, 254 expected = 255 Expected( 256 iconResource = WIFI_NO_NETWORK, 257 contentDescription = { context -> 258 "${context.getString(WIFI_NO_CONNECTION)}," + 259 context.getString(NO_INTERNET) 260 }, 261 description = "No network icon", 262 ), 263 ), 264 TestCase( 265 alwaysShowIconWhenEnabled = true, 266 network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 4), 267 expected = 268 Expected( 269 iconResource = WIFI_NO_INTERNET_ICONS[4], 270 contentDescription = { context -> 271 "${context.getString(WIFI_CONNECTION_STRENGTH[4])}," + 272 context.getString(NO_INTERNET) 273 }, 274 description = "No internet level 4 icon", 275 ), 276 ), 277 TestCase( 278 alwaysShowIconWhenEnabled = true, 279 network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2), 280 expected = 281 Expected( 282 iconResource = WIFI_FULL_ICONS[2], 283 contentDescription = { context -> 284 context.getString(WIFI_CONNECTION_STRENGTH[2]) 285 }, 286 description = "Full internet level 2 icon", 287 ), 288 ), 289 290 // hasDataCapabilities = false => all Inactive and Active networks shown 291 TestCase( 292 hasDataCapabilities = false, 293 network = WifiNetworkModel.Inactive, 294 expected = 295 Expected( 296 iconResource = WIFI_NO_NETWORK, 297 contentDescription = { context -> 298 "${context.getString(WIFI_NO_CONNECTION)}," + 299 context.getString(NO_INTERNET) 300 }, 301 description = "No network icon", 302 ), 303 ), 304 TestCase( 305 hasDataCapabilities = false, 306 network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 2), 307 expected = 308 Expected( 309 iconResource = WIFI_NO_INTERNET_ICONS[2], 310 contentDescription = { context -> 311 "${context.getString(WIFI_CONNECTION_STRENGTH[2])}," + 312 context.getString(NO_INTERNET) 313 }, 314 description = "No internet level 2 icon", 315 ), 316 ), 317 TestCase( 318 hasDataCapabilities = false, 319 network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 0), 320 expected = 321 Expected( 322 iconResource = WIFI_FULL_ICONS[0], 323 contentDescription = { context -> 324 context.getString(WIFI_CONNECTION_STRENGTH[0]) 325 }, 326 description = "Full internet level 0 icon", 327 ), 328 ), 329 330 // isDefault = true => all Inactive and Active networks shown 331 TestCase( 332 isDefault = true, 333 network = WifiNetworkModel.Inactive, 334 expected = 335 Expected( 336 iconResource = WIFI_NO_NETWORK, 337 contentDescription = { context -> 338 "${context.getString(WIFI_NO_CONNECTION)}," + 339 context.getString(NO_INTERNET) 340 }, 341 description = "No network icon", 342 ), 343 ), 344 TestCase( 345 isDefault = true, 346 network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 3), 347 expected = 348 Expected( 349 iconResource = WIFI_NO_INTERNET_ICONS[3], 350 contentDescription = { context -> 351 "${context.getString(WIFI_CONNECTION_STRENGTH[3])}," + 352 context.getString(NO_INTERNET) 353 }, 354 description = "No internet level 3 icon", 355 ), 356 ), 357 TestCase( 358 isDefault = true, 359 network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 1), 360 expected = 361 Expected( 362 iconResource = WIFI_FULL_ICONS[1], 363 contentDescription = { context -> 364 context.getString(WIFI_CONNECTION_STRENGTH[1]) 365 }, 366 description = "Full internet level 1 icon", 367 ), 368 ), 369 370 // network = CarrierMerged => not shown 371 TestCase( 372 enabled = true, 373 isDefault = true, 374 forceHidden = false, 375 network = 376 WifiNetworkModel.CarrierMerged(NETWORK_ID, subscriptionId = 1, level = 1), 377 expected = null, 378 ), 379 380 // isDefault = false => no networks shown 381 TestCase( 382 isDefault = false, 383 network = WifiNetworkModel.Inactive, 384 expected = null, 385 ), 386 TestCase( 387 isDefault = false, 388 network = WifiNetworkModel.Unavailable, 389 expected = null, 390 ), 391 TestCase( 392 isDefault = false, 393 network = WifiNetworkModel.Active(NETWORK_ID, isValidated = false, level = 3), 394 expected = null, 395 ), 396 397 // Even though this network is active and validated, we still doesn't want it shown 398 // because wifi isn't the default connection (b/272509965). 399 TestCase( 400 isDefault = false, 401 network = WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 4), 402 expected = null, 403 ), 404 ) 405 } 406 } 407 408 private val IMMEDIATE = Dispatchers.Main.immediate 409 private const val NETWORK_ID = 789 410