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