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