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