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