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.android.systemui.testKosmos 46 import com.google.common.truth.Truth.assertThat 47 import kotlinx.coroutines.CoroutineScope 48 import kotlinx.coroutines.Dispatchers 49 import kotlinx.coroutines.cancel 50 import kotlinx.coroutines.flow.MutableStateFlow 51 import kotlinx.coroutines.flow.launchIn 52 import kotlinx.coroutines.runBlocking 53 import kotlinx.coroutines.yield 54 import org.junit.After 55 import org.junit.Before 56 import org.junit.Test 57 import org.junit.runner.RunWith 58 import org.junit.runners.Parameterized 59 import org.junit.runners.Parameterized.Parameters 60 import org.mockito.Mock 61 import org.mockito.Mockito.`when` as whenever 62 import org.mockito.MockitoAnnotations 63 64 @SmallTest 65 @RunWith(Parameterized::class) 66 internal class WifiViewModelIconParameterizedTest(private val testCase: TestCase) : 67 SysuiTestCase() { 68 private val kosmos = testKosmos() 69 70 private lateinit var underTest: WifiViewModel 71 72 @Mock private lateinit var tableLogBuffer: TableLogBuffer 73 @Mock private lateinit var connectivityConstants: ConnectivityConstants 74 @Mock private lateinit var wifiConstants: WifiConstants 75 private lateinit var airplaneModeRepository: FakeAirplaneModeRepository 76 private lateinit var connectivityRepository: FakeConnectivityRepository 77 private lateinit var wifiRepository: FakeWifiRepository 78 private lateinit var interactor: WifiInteractor 79 private lateinit var airplaneModeViewModel: AirplaneModeViewModel 80 private lateinit var scope: CoroutineScope 81 82 @Before 83 fun setUp() { 84 MockitoAnnotations.initMocks(this) 85 airplaneModeRepository = FakeAirplaneModeRepository() 86 connectivityRepository = FakeConnectivityRepository() 87 wifiRepository = FakeWifiRepository() 88 wifiRepository.setIsWifiEnabled(true) 89 scope = CoroutineScope(IMMEDIATE) 90 interactor = WifiInteractorImpl(connectivityRepository, wifiRepository, scope) 91 airplaneModeViewModel = 92 AirplaneModeViewModelImpl( 93 AirplaneModeInteractor( 94 airplaneModeRepository, 95 connectivityRepository, 96 kosmos.fakeMobileConnectionsRepository, 97 ), 98 tableLogBuffer, 99 scope, 100 ) 101 } 102 103 @After 104 fun tearDown() { 105 scope.cancel() 106 } 107 108 @Test 109 fun wifiIcon() = 110 runBlocking(IMMEDIATE) { 111 wifiRepository.setIsWifiEnabled(testCase.enabled) 112 wifiRepository.setIsWifiDefault(testCase.isDefault) 113 connectivityRepository.setForceHiddenIcons( 114 if (testCase.forceHidden) { 115 setOf(ConnectivitySlot.WIFI) 116 } else { 117 setOf() 118 } 119 ) 120 whenever(wifiConstants.alwaysShowIconIfEnabled) 121 .thenReturn(testCase.alwaysShowIconWhenEnabled) 122 whenever(connectivityConstants.hasDataCapabilities) 123 .thenReturn(testCase.hasDataCapabilities) 124 underTest = 125 WifiViewModel( 126 airplaneModeViewModel, 127 shouldShowSignalSpacerProvider = { MutableStateFlow(false) }, 128 connectivityConstants, 129 context, 130 tableLogBuffer, 131 interactor, 132 scope, 133 wifiConstants, 134 ) 135 136 val iconFlow = underTest.wifiIcon 137 val job = iconFlow.launchIn(this) 138 139 // WHEN we set a certain network 140 wifiRepository.setWifiNetwork(testCase.network) 141 yield() 142 143 // THEN we get the expected icon 144 val actualIcon = iconFlow.value 145 when (testCase.expected) { 146 null -> { 147 assertThat(actualIcon).isInstanceOf(WifiIcon.Hidden::class.java) 148 } 149 else -> { 150 assertThat(actualIcon).isInstanceOf(WifiIcon.Visible::class.java) 151 val actualIconVisible = actualIcon as WifiIcon.Visible 152 assertThat(actualIconVisible.icon.res).isEqualTo(testCase.expected.iconResource) 153 val expectedContentDescription = 154 testCase.expected.contentDescription.invoke(context) 155 assertThat(actualIconVisible.contentDescription.loadContentDescription(context)) 156 .isEqualTo(expectedContentDescription) 157 } 158 } 159 160 job.cancel() 161 } 162 163 internal data class Expected( 164 /** The resource that should be used for the icon. */ 165 @DrawableRes val iconResource: Int, 166 167 /** A function that, given a context, calculates the correct content description string. */ 168 val contentDescription: (Context) -> String, 169 170 /** A human-readable description used for the test names. */ 171 val description: String, 172 ) { 173 override fun toString() = description 174 } 175 176 // Note: We use default values for the boolean parameters to reflect a "typical configuration" 177 // for wifi. This allows each TestCase to only define the parameter values that are critical 178 // for the test function. 179 internal data class TestCase( 180 val enabled: Boolean = true, 181 val forceHidden: Boolean = false, 182 val alwaysShowIconWhenEnabled: Boolean = false, 183 val hasDataCapabilities: Boolean = true, 184 val isDefault: Boolean = false, 185 val network: WifiNetworkModel, 186 187 /** The expected output. Null if we expect the output to be hidden. */ 188 val expected: Expected? 189 ) { 190 override fun toString(): String { 191 return "when INPUT(enabled=$enabled, " + 192 "forceHidden=$forceHidden, " + 193 "showWhenEnabled=$alwaysShowIconWhenEnabled, " + 194 "hasDataCaps=$hasDataCapabilities, " + 195 "isDefault=$isDefault, " + 196 "network=$network) then " + 197 "EXPECTED($expected)" 198 } 199 } 200 201 companion object { 202 @Parameters(name = "{0}") @JvmStatic fun data(): Collection<TestCase> = testData 203 204 private val testData: List<TestCase> = 205 listOf( 206 // Enabled = false => no networks shown 207 TestCase( 208 enabled = false, 209 network = WifiNetworkModel.CarrierMerged.of(subscriptionId = 1, level = 1), 210 expected = null, 211 ), 212 TestCase( 213 enabled = false, 214 network = WifiNetworkModel.Inactive(), 215 expected = null, 216 ), 217 TestCase( 218 enabled = false, 219 network = WifiNetworkModel.Active.of(isValidated = false, level = 1), 220 expected = null, 221 ), 222 TestCase( 223 enabled = false, 224 network = WifiNetworkModel.Active.of(isValidated = true, level = 3), 225 expected = null, 226 ), 227 228 // forceHidden = true => no networks shown 229 TestCase( 230 forceHidden = true, 231 network = WifiNetworkModel.CarrierMerged.of(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.of(isValidated = false, level = 2), 242 expected = null, 243 ), 244 TestCase( 245 forceHidden = true, 246 network = WifiNetworkModel.Active.of(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.of(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.of(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.of(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.of(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.of(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.of(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 = WifiNetworkModel.CarrierMerged.of(subscriptionId = 1, level = 1), 376 expected = null, 377 ), 378 379 // isDefault = false => no networks shown 380 TestCase( 381 isDefault = false, 382 network = WifiNetworkModel.Inactive(), 383 expected = null, 384 ), 385 TestCase( 386 isDefault = false, 387 network = WifiNetworkModel.Unavailable, 388 expected = null, 389 ), 390 TestCase( 391 isDefault = false, 392 network = WifiNetworkModel.Active.of(isValidated = false, level = 3), 393 expected = null, 394 ), 395 396 // Even though this network is active and validated, we still doesn't want it shown 397 // because wifi isn't the default connection (b/272509965). 398 TestCase( 399 isDefault = false, 400 network = WifiNetworkModel.Active.of(isValidated = true, level = 4), 401 expected = null, 402 ), 403 ) 404 } 405 } 406 407 private val IMMEDIATE = Dispatchers.Main.immediate 408