1 /*
2  * 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.mobile.data.repository.demo
18 
19 import android.telephony.Annotation
20 import android.telephony.TelephonyManager
21 import android.telephony.TelephonyManager.DATA_ACTIVITY_NONE
22 import androidx.test.filters.SmallTest
23 import com.android.settingslib.SignalIcon
24 import com.android.settingslib.mobile.TelephonyIcons
25 import com.android.systemui.SysuiTestCase
26 import com.android.systemui.log.table.TableLogBufferFactory
27 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
28 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel
29 import com.android.systemui.statusbar.pipeline.mobile.data.repository.demo.model.FakeNetworkEventModel
30 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
31 import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.DemoModeWifiDataSource
32 import com.android.systemui.statusbar.pipeline.wifi.data.repository.demo.model.FakeWifiEventModel
33 import com.android.systemui.util.mockito.mock
34 import com.android.systemui.util.mockito.whenever
35 import com.android.systemui.util.time.FakeSystemClock
36 import com.google.common.truth.Truth.assertThat
37 import kotlinx.coroutines.ExperimentalCoroutinesApi
38 import kotlinx.coroutines.Job
39 import kotlinx.coroutines.cancel
40 import kotlinx.coroutines.flow.MutableStateFlow
41 import kotlinx.coroutines.launch
42 import kotlinx.coroutines.test.TestScope
43 import kotlinx.coroutines.test.UnconfinedTestDispatcher
44 import kotlinx.coroutines.test.runTest
45 import org.junit.After
46 import org.junit.Before
47 import org.junit.Test
48 import org.junit.runner.RunWith
49 import org.junit.runners.Parameterized
50 import org.junit.runners.Parameterized.Parameters
51 
52 /**
53  * Parameterized test for all of the common values of [FakeNetworkEventModel]. This test simply
54  * verifies that passing the given model to [DemoMobileConnectionsRepository] results in the correct
55  * flows emitting from the given connection.
56  */
57 @OptIn(ExperimentalCoroutinesApi::class)
58 @SmallTest
59 @RunWith(Parameterized::class)
60 internal class DemoMobileConnectionParameterizedTest(private val testCase: TestCase) :
61     SysuiTestCase() {
62 
63     private val testDispatcher = UnconfinedTestDispatcher()
64     private val testScope = TestScope(testDispatcher)
65 
66     private val logFactory =
67         TableLogBufferFactory(
68             mock(),
69             FakeSystemClock(),
70             mock(),
71             testDispatcher,
72             testScope.backgroundScope,
73         )
74 
75     private val fakeNetworkEventFlow = MutableStateFlow<FakeNetworkEventModel?>(null)
76     private val fakeWifiEventFlow = MutableStateFlow<FakeWifiEventModel?>(null)
77 
78     private lateinit var connectionsRepo: DemoMobileConnectionsRepository
79     private lateinit var underTest: DemoMobileConnectionRepository
80     private lateinit var mockDataSource: DemoModeMobileConnectionDataSource
81     private lateinit var mockWifiDataSource: DemoModeWifiDataSource
82 
83     @Before
84     fun setUp() {
85         // The data source only provides one API, so we can mock it with a flow here for convenience
86         mockDataSource =
87             mock<DemoModeMobileConnectionDataSource>().also {
88                 whenever(it.mobileEvents).thenReturn(fakeNetworkEventFlow)
89             }
90         mockWifiDataSource =
91             mock<DemoModeWifiDataSource>().also {
92                 whenever(it.wifiEvents).thenReturn(fakeWifiEventFlow)
93             }
94 
95         connectionsRepo =
96             DemoMobileConnectionsRepository(
97                 mobileDataSource = mockDataSource,
98                 wifiDataSource = mockWifiDataSource,
99                 scope = testScope.backgroundScope,
100                 context = context,
101                 logFactory = logFactory,
102             )
103 
104         connectionsRepo.startProcessingCommands()
105     }
106 
107     @After
108     fun tearDown() {
109         testScope.cancel()
110     }
111 
112     @Test
113     fun demoNetworkData() =
114         testScope.runTest {
115             val networkModel =
116                 FakeNetworkEventModel.Mobile(
117                     level = testCase.level,
118                     dataType = testCase.dataType,
119                     subId = testCase.subId,
120                     carrierId = testCase.carrierId,
121                     inflateStrength = testCase.inflateStrength,
122                     activity = testCase.activity,
123                     carrierNetworkChange = testCase.carrierNetworkChange,
124                     roaming = testCase.roaming,
125                     name = "demo name",
126                 )
127 
128             fakeNetworkEventFlow.value = networkModel
129             underTest = connectionsRepo.getRepoForSubId(subId)
130 
131             assertConnection(underTest, networkModel)
132         }
133 
134     private fun TestScope.startCollection(conn: DemoMobileConnectionRepository): Job {
135         val job = launch {
136             launch { conn.cdmaLevel.collect {} }
137             launch { conn.primaryLevel.collect {} }
138             launch { conn.dataActivityDirection.collect {} }
139             launch { conn.carrierNetworkChangeActive.collect {} }
140             launch { conn.isRoaming.collect {} }
141             launch { conn.networkName.collect {} }
142             launch { conn.carrierName.collect {} }
143             launch { conn.isEmergencyOnly.collect {} }
144             launch { conn.dataConnectionState.collect {} }
145         }
146         return job
147     }
148 
149     private fun TestScope.assertConnection(
150         conn: DemoMobileConnectionRepository,
151         model: FakeNetworkEventModel
152     ) {
153         val job = startCollection(underTest)
154         when (model) {
155             is FakeNetworkEventModel.Mobile -> {
156                 assertThat(conn.subId).isEqualTo(model.subId)
157                 assertThat(conn.cdmaLevel.value).isEqualTo(model.level)
158                 assertThat(conn.primaryLevel.value).isEqualTo(model.level)
159                 assertThat(conn.dataActivityDirection.value)
160                     .isEqualTo((model.activity ?: DATA_ACTIVITY_NONE).toMobileDataActivityModel())
161                 assertThat(conn.carrierNetworkChangeActive.value)
162                     .isEqualTo(model.carrierNetworkChange)
163                 assertThat(conn.isRoaming.value).isEqualTo(model.roaming)
164                 assertThat(conn.networkName.value)
165                     .isEqualTo(NetworkNameModel.IntentDerived(model.name))
166                 assertThat(conn.carrierName.value)
167                     .isEqualTo(NetworkNameModel.SubscriptionDerived("${model.name} ${model.subId}"))
168 
169                 // TODO(b/261029387): check these once we start handling them
170                 assertThat(conn.isEmergencyOnly.value).isFalse()
171                 assertThat(conn.isGsm.value).isFalse()
172                 assertThat(conn.dataConnectionState.value).isEqualTo(DataConnectionState.Connected)
173             }
174             // MobileDisabled isn't combinatorial in nature, and is tested in
175             // DemoMobileConnectionsRepositoryTest.kt
176             else -> {}
177         }
178 
179         job.cancel()
180     }
181 
182     /** Matches [FakeNetworkEventModel] */
183     internal data class TestCase(
184         val level: Int,
185         val dataType: SignalIcon.MobileIconGroup,
186         val subId: Int,
187         val carrierId: Int,
188         val inflateStrength: Boolean,
189         @Annotation.DataActivityType val activity: Int,
190         val carrierNetworkChange: Boolean,
191         val roaming: Boolean,
192         val name: String,
193     ) {
194         override fun toString(): String {
195             return "INPUT(level=$level, " +
196                 "dataType=${dataType.name}, " +
197                 "subId=$subId, " +
198                 "carrierId=$carrierId, " +
199                 "inflateStrength=$inflateStrength, " +
200                 "activity=$activity, " +
201                 "carrierNetworkChange=$carrierNetworkChange, " +
202                 "roaming=$roaming, " +
203                 "name=$name)"
204         }
205 
206         // Convenience for iterating test data and creating new cases
207         fun modifiedBy(
208             level: Int? = null,
209             dataType: SignalIcon.MobileIconGroup? = null,
210             subId: Int? = null,
211             carrierId: Int? = null,
212             inflateStrength: Boolean? = null,
213             @Annotation.DataActivityType activity: Int? = null,
214             carrierNetworkChange: Boolean? = null,
215             roaming: Boolean? = null,
216             name: String? = null,
217         ): TestCase =
218             TestCase(
219                 level = level ?: this.level,
220                 dataType = dataType ?: this.dataType,
221                 subId = subId ?: this.subId,
222                 carrierId = carrierId ?: this.carrierId,
223                 inflateStrength = inflateStrength ?: this.inflateStrength,
224                 activity = activity ?: this.activity,
225                 carrierNetworkChange = carrierNetworkChange ?: this.carrierNetworkChange,
226                 roaming = roaming ?: this.roaming,
227                 name = name ?: this.name,
228             )
229     }
230 
231     companion object {
232         private val subId = 1
233 
234         private val booleanList = listOf(true, false)
235         private val levels = listOf(0, 1, 2, 3)
236         private val dataTypes =
237             listOf(
238                 TelephonyIcons.THREE_G,
239                 TelephonyIcons.LTE,
240                 TelephonyIcons.FOUR_G,
241                 TelephonyIcons.NR_5G,
242                 TelephonyIcons.NR_5G_PLUS,
243             )
244         private val carrierIds = listOf(1, 10, 100)
245         private val inflateStrength = booleanList
246         private val activity =
247             listOf(
248                 TelephonyManager.DATA_ACTIVITY_NONE,
249                 TelephonyManager.DATA_ACTIVITY_IN,
250                 TelephonyManager.DATA_ACTIVITY_OUT,
251                 TelephonyManager.DATA_ACTIVITY_INOUT
252             )
253         private val carrierNetworkChange = booleanList
254         // false first so the base case doesn't have roaming set (more common)
255         private val roaming = listOf(false, true)
256         private val names = listOf("name 1", "name 2")
257 
258         @Parameters(name = "{0}") @JvmStatic fun data() = testData()
259 
260         /**
261          * Generate some test data. For the sake of convenience, we'll parameterize only non-null
262          * network event data. So given the lists of test data:
263          * ```
264          *    list1 = [1, 2, 3]
265          *    list2 = [false, true]
266          *    list3 = [a, b, c]
267          * ```
268          *
269          * We'll generate test cases for:
270          *
271          * Test (1, false, a) Test (2, false, a) Test (3, false, a) Test (1, true, a) Test (1,
272          * false, b) Test (1, false, c)
273          *
274          * NOTE: this is not a combinatorial product of all of the possible sets of parameters.
275          * Since this test is built to exercise demo mode, the general approach is to define a
276          * fully-formed "base case", and from there to make sure to use every valid parameter once,
277          * by defining the rest of the test cases against the base case. Specific use-cases can be
278          * added to the non-parameterized test, or manually below the generated test cases.
279          */
280         private fun testData(): List<TestCase> {
281             val testSet = mutableSetOf<TestCase>()
282 
283             val baseCase =
284                 TestCase(
285                     levels.first(),
286                     dataTypes.first(),
287                     subId,
288                     carrierIds.first(),
289                     inflateStrength.first(),
290                     activity.first(),
291                     carrierNetworkChange.first(),
292                     roaming.first(),
293                     names.first(),
294                 )
295 
296             val tail =
297                 sequenceOf(
298                         levels.map { baseCase.modifiedBy(level = it) },
299                         dataTypes.map { baseCase.modifiedBy(dataType = it) },
300                         carrierIds.map { baseCase.modifiedBy(carrierId = it) },
301                         inflateStrength.map { baseCase.modifiedBy(inflateStrength = it) },
302                         activity.map { baseCase.modifiedBy(activity = it) },
303                         carrierNetworkChange.map { baseCase.modifiedBy(carrierNetworkChange = it) },
304                         roaming.map { baseCase.modifiedBy(roaming = it) },
305                         names.map { baseCase.modifiedBy(name = it) },
306                     )
307                     .flatten()
308 
309             testSet.add(baseCase)
310             tail.toCollection(testSet)
311 
312             return testSet.toList()
313         }
314     }
315 }
316