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