1 /* <lambda>null2 * Copyright (C) 2023 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.prod 18 19 import android.net.ConnectivityManager 20 import android.telephony.ServiceState 21 import android.telephony.TelephonyCallback 22 import android.telephony.TelephonyCallback.CarrierNetworkListener 23 import android.telephony.TelephonyCallback.DataActivityListener 24 import android.telephony.TelephonyCallback.DataConnectionStateListener 25 import android.telephony.TelephonyCallback.DataEnabledListener 26 import android.telephony.TelephonyCallback.DisplayInfoListener 27 import android.telephony.TelephonyCallback.ServiceStateListener 28 import android.telephony.TelephonyDisplayInfo 29 import android.telephony.TelephonyManager 30 import android.telephony.TelephonyManager.DATA_ACTIVITY_INOUT 31 import android.telephony.TelephonyManager.NETWORK_TYPE_LTE 32 import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN 33 import androidx.test.filters.SmallTest 34 import com.android.systemui.SysuiTestCase 35 import com.android.systemui.flags.FakeFeatureFlagsClassic 36 import com.android.systemui.flags.Flags 37 import com.android.systemui.log.table.TableLogBuffer 38 import com.android.systemui.statusbar.pipeline.mobile.data.MobileInputLogger 39 import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState 40 import com.android.systemui.statusbar.pipeline.mobile.data.model.NetworkNameModel 41 import com.android.systemui.statusbar.pipeline.mobile.data.model.ResolvedNetworkType 42 import com.android.systemui.statusbar.pipeline.mobile.data.model.SubscriptionModel 43 import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfig 44 import com.android.systemui.statusbar.pipeline.mobile.data.model.SystemUiCarrierConfigTest 45 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository 46 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.getTelephonyCallbackForType 47 import com.android.systemui.statusbar.pipeline.mobile.data.repository.prod.MobileTelephonyHelpers.signalStrength 48 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy 49 import com.android.systemui.statusbar.pipeline.shared.data.model.DataActivityModel 50 import com.android.systemui.util.mockito.mock 51 import com.android.systemui.util.mockito.whenever 52 import com.google.common.truth.Truth.assertThat 53 import kotlinx.coroutines.ExperimentalCoroutinesApi 54 import kotlinx.coroutines.flow.StateFlow 55 import kotlinx.coroutines.flow.launchIn 56 import kotlinx.coroutines.flow.onEach 57 import kotlinx.coroutines.test.TestScope 58 import kotlinx.coroutines.test.UnconfinedTestDispatcher 59 import kotlinx.coroutines.test.runTest 60 import org.junit.Before 61 import org.junit.Test 62 import org.mockito.Mock 63 import org.mockito.MockitoAnnotations 64 65 /** 66 * Test class to stress test the TelephonyCallbacks that we listen to. In particular, the callbacks 67 * all come back in on a single listener (for reasons defined in the system). This test is built to 68 * ensure that we don't miss any important callbacks. 69 * 70 * Kind of like an interaction test case build just for [TelephonyCallback] 71 * 72 * The list of telephony callbacks we use is: 73 * - [TelephonyCallback.CarrierNetworkListener] 74 * - [TelephonyCallback.DataActivityListener] 75 * - [TelephonyCallback.DataConnectionStateListener] 76 * - [TelephonyCallback.DataEnabledListener] 77 * - [TelephonyCallback.DisplayInfoListener] 78 * - [TelephonyCallback.ServiceStateListener] 79 * - [TelephonyCallback.SignalStrengthsListener] 80 * 81 * Because each of these callbacks comes in on the same callbackFlow, collecting on a field backed 82 * by only a single callback can immediately create backpressure on the other fields related to a 83 * mobile connection. 84 * 85 * This test should be designed to test _at least_ each individual callback in a smoke-test fashion. 86 * The way we will achieve this is as follows: 87 * 1. Start up a listener (A) collecting on a field which is _not under test_ 88 * 2. Send a single event to a telephony callback which supports the field under test (B) 89 * 3. Send many (may be as few as 2) events to the callback backing A to ensure we start seeing 90 * backpressure on other fields NOTE: poor handling of backpressure here would normally cause B 91 * to get dropped 92 * 4. Start up a new collector for B 93 * 5. Assert that B has the state sent in step #2 94 */ 95 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED") 96 @OptIn(ExperimentalCoroutinesApi::class) 97 @SmallTest 98 class MobileConnectionTelephonySmokeTests : SysuiTestCase() { 99 private lateinit var underTest: MobileConnectionRepositoryImpl 100 private lateinit var connectionsRepo: FakeMobileConnectionsRepository 101 102 private val flags = 103 FakeFeatureFlagsClassic().also { it.set(Flags.ROAMING_INDICATOR_VIA_DISPLAY_INFO, true) } 104 105 @Mock private lateinit var connectivityManager: ConnectivityManager 106 @Mock private lateinit var telephonyManager: TelephonyManager 107 @Mock private lateinit var logger: MobileInputLogger 108 @Mock private lateinit var tableLogger: TableLogBuffer 109 @Mock private lateinit var subscriptionModel: StateFlow<SubscriptionModel?> 110 111 private val mobileMappings = FakeMobileMappingsProxy() 112 private val systemUiCarrierConfig = 113 SystemUiCarrierConfig( 114 SUB_1_ID, 115 SystemUiCarrierConfigTest.createTestConfig(), 116 ) 117 118 private val testDispatcher = UnconfinedTestDispatcher() 119 private val testScope = TestScope(testDispatcher) 120 121 @Before 122 fun setUp() { 123 MockitoAnnotations.initMocks(this) 124 whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID) 125 126 connectionsRepo = 127 FakeMobileConnectionsRepository( 128 mobileMappings, 129 tableLogger, 130 ) 131 132 underTest = 133 MobileConnectionRepositoryImpl( 134 SUB_1_ID, 135 context, 136 subscriptionModel, 137 DEFAULT_NAME, 138 SEP, 139 connectivityManager, 140 telephonyManager, 141 systemUiCarrierConfig, 142 fakeBroadcastDispatcher, 143 mobileMappings, 144 testDispatcher, 145 logger, 146 tableLogger, 147 flags, 148 testScope.backgroundScope, 149 ) 150 } 151 152 @Test 153 fun carrierNetworkChangeListener_noisyActivity() = 154 testScope.runTest { 155 var latest: Boolean? = null 156 157 // Start collecting data activity; don't care about the result 158 val activityJob = underTest.dataActivityDirection.launchIn(this) 159 val activityCallback = getTelephonyCallbackForType<DataActivityListener>() 160 161 val callback = getTelephonyCallbackForType<CarrierNetworkListener>() 162 callback.onCarrierNetworkChange(true) 163 164 flipActivity(100, activityCallback) 165 166 val job = underTest.carrierNetworkChangeActive.onEach { latest = it }.launchIn(this) 167 168 assertThat(latest).isTrue() 169 170 activityJob.cancel() 171 job.cancel() 172 } 173 174 @Test 175 fun dataActivityLate_noisyDisplayInfo() = 176 testScope.runTest { 177 var latest: DataActivityModel? = null 178 179 // start collecting displayInfo; don't care about the result 180 val displayInfoJob = underTest.resolvedNetworkType.launchIn(this) 181 182 val activityCallback = getTelephonyCallbackForType<DataActivityListener>() 183 activityCallback.onDataActivity(DATA_ACTIVITY_INOUT) 184 185 val displayInfoCallback = getTelephonyCallbackForType<DisplayInfoListener>() 186 val type1 = NETWORK_TYPE_UNKNOWN 187 val type2 = NETWORK_TYPE_LTE 188 val t1 = 189 mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type1) } 190 val t2 = 191 mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type2) } 192 193 flipDisplayInfo(100, listOf(t1, t2), displayInfoCallback) 194 195 val job = underTest.dataActivityDirection.onEach { latest = it }.launchIn(this) 196 197 assertThat(latest) 198 .isEqualTo( 199 DataActivityModel( 200 hasActivityIn = true, 201 hasActivityOut = true, 202 ) 203 ) 204 205 displayInfoJob.cancel() 206 job.cancel() 207 } 208 209 @Test 210 fun dataConnectionStateListener_noisyActivity() = 211 testScope.runTest { 212 var latest: DataConnectionState? = null 213 214 // Start collecting data activity; don't care about the result 215 val activityJob = underTest.dataActivityDirection.launchIn(this) 216 217 val connectionCallback = getTelephonyCallbackForType<DataConnectionStateListener>() 218 val activityCallback = getTelephonyCallbackForType<DataActivityListener>() 219 220 connectionCallback.onDataConnectionStateChanged( 221 TelephonyManager.DATA_CONNECTED, 222 200 /* unused */ 223 ) 224 225 flipActivity(100, activityCallback) 226 227 val connectionJob = underTest.dataConnectionState.onEach { latest = it }.launchIn(this) 228 229 assertThat(latest).isEqualTo(DataConnectionState.Connected) 230 231 activityJob.cancel() 232 connectionJob.cancel() 233 } 234 235 @Test 236 fun dataEnabledLate_noisyActivity() = 237 testScope.runTest { 238 var latest: Boolean? = null 239 240 // Start collecting data activity; don't care about the result 241 val activityJob = underTest.dataActivityDirection.launchIn(this) 242 243 val enabledCallback = getTelephonyCallbackForType<DataEnabledListener>() 244 val activityCallback = getTelephonyCallbackForType<DataActivityListener>() 245 246 enabledCallback.onDataEnabledChanged(true, 1 /* unused */) 247 248 flipActivity(100, activityCallback) 249 250 val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this) 251 252 assertThat(latest).isTrue() 253 254 activityJob.cancel() 255 job.cancel() 256 } 257 258 @Test 259 fun displayInfoLate_noisyActivity() = 260 testScope.runTest { 261 var latest: ResolvedNetworkType? = null 262 263 // Start collecting data activity; don't care about the result 264 val activityJob = underTest.dataActivityDirection.launchIn(this) 265 266 val displayInfoCallback = getTelephonyCallbackForType<DisplayInfoListener>() 267 val activityCallback = getTelephonyCallbackForType<DataActivityListener>() 268 269 val type = NETWORK_TYPE_LTE 270 val expected = ResolvedNetworkType.DefaultNetworkType(mobileMappings.toIconKey(type)) 271 val ti = mock<TelephonyDisplayInfo>().also { whenever(it.networkType).thenReturn(type) } 272 displayInfoCallback.onDisplayInfoChanged(ti) 273 274 flipActivity(100, activityCallback) 275 276 val job = underTest.resolvedNetworkType.onEach { latest = it }.launchIn(this) 277 278 assertThat(latest).isEqualTo(expected) 279 280 activityJob.cancel() 281 job.cancel() 282 } 283 284 @Test 285 fun serviceStateListener_noisyActivity() = 286 testScope.runTest { 287 var latest: Boolean? = null 288 289 // Start collecting data activity; don't care about the result 290 val activityJob = underTest.dataActivityDirection.launchIn(this) 291 292 val serviceStateCallback = getTelephonyCallbackForType<ServiceStateListener>() 293 val activityCallback = getTelephonyCallbackForType<DataActivityListener>() 294 295 // isEmergencyOnly comes in 296 val serviceState = ServiceState() 297 serviceState.isEmergencyOnly = true 298 serviceStateCallback.onServiceStateChanged(serviceState) 299 300 flipActivity(100, activityCallback) 301 302 val job = underTest.isEmergencyOnly.onEach { latest = it }.launchIn(this) 303 304 assertThat(latest).isTrue() 305 306 activityJob.cancel() 307 job.cancel() 308 } 309 310 @Test 311 fun signalStrengthsListenerLate_noisyActivity() = 312 testScope.runTest { 313 var latest: Int? = null 314 315 // Start collecting data activity; don't care about the result 316 val activityJob = underTest.dataActivityDirection.launchIn(this) 317 val activityCallback = getTelephonyCallbackForType<DataActivityListener>() 318 319 val callback = getTelephonyCallbackForType<TelephonyCallback.SignalStrengthsListener>() 320 val strength = signalStrength(gsmLevel = 1, cdmaLevel = 2, isGsm = true) 321 callback.onSignalStrengthsChanged(strength) 322 323 flipActivity(100, activityCallback) 324 325 val job = underTest.cdmaLevel.onEach { latest = it }.launchIn(this) 326 327 assertThat(latest).isEqualTo(2) 328 329 activityJob.cancel() 330 job.cancel() 331 } 332 333 private fun flipActivity( 334 times: Int, 335 callback: DataActivityListener, 336 ) { 337 repeat(times) { index -> callback.onDataActivity(index % 4) } 338 } 339 340 private fun flipDisplayInfo( 341 times: Int, 342 infos: List<TelephonyDisplayInfo>, 343 callback: DisplayInfoListener, 344 ) { 345 val len = infos.size 346 repeat(times) { index -> callback.onDisplayInfoChanged(infos[index % len]) } 347 } 348 349 private inline fun <reified T> getTelephonyCallbackForType(): T { 350 return getTelephonyCallbackForType(telephonyManager) 351 } 352 353 companion object { 354 private const val SUB_1_ID = 1 355 356 private val DEFAULT_NAME = NetworkNameModel.Default("default name") 357 private const val SEP = "-" 358 } 359 } 360