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