1 /*
2  * Copyright (C) 2024 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.settings.network.telephony.scan
18 
19 import android.content.Context
20 import android.telephony.AccessNetworkConstants.AccessNetworkType
21 import android.telephony.CellIdentityCdma
22 import android.telephony.CellIdentityGsm
23 import android.telephony.CellIdentityLte
24 import android.telephony.CellInfoCdma
25 import android.telephony.CellInfoGsm
26 import android.telephony.CellInfoLte
27 import android.telephony.NetworkScan
28 import android.telephony.NetworkScanRequest
29 import android.telephony.PhoneCapability
30 import android.telephony.TelephonyManager
31 import android.telephony.TelephonyManager.NETWORK_CLASS_BITMASK_5G
32 import android.telephony.TelephonyScanManager
33 import androidx.test.core.app.ApplicationProvider
34 import androidx.test.ext.junit.runners.AndroidJUnit4
35 import com.android.settingslib.spa.testutils.firstWithTimeoutOrNull
36 import com.android.settingslib.spa.testutils.toListWithTimeout
37 import com.google.common.truth.Truth.assertThat
38 import kotlinx.coroutines.async
39 import kotlinx.coroutines.delay
40 import kotlinx.coroutines.runBlocking
41 import org.junit.Test
42 import org.junit.runner.RunWith
43 import org.mockito.kotlin.any
44 import org.mockito.kotlin.argThat
45 import org.mockito.kotlin.doAnswer
46 import org.mockito.kotlin.doReturn
47 import org.mockito.kotlin.mock
48 import org.mockito.kotlin.spy
49 import org.mockito.kotlin.stub
50 import org.mockito.kotlin.verify
51 
52 @RunWith(AndroidJUnit4::class)
53 class NetworkScanRepositoryTest {
54 
55     private var callback: TelephonyScanManager.NetworkScanCallback? = null
56 
<lambda>null57     private val mockTelephonyManager = mock<TelephonyManager> {
58         on { createForSubscriptionId(SUB_ID) } doReturn mock
59         on { requestNetworkScan(any(), any(), any()) } doAnswer {
60             callback = it.arguments[2] as TelephonyScanManager.NetworkScanCallback
61             mock<NetworkScan>()
62         }
63     }
64 
<lambda>null65     private val context: Context = spy(ApplicationProvider.getApplicationContext()) {
66         on { getSystemService(TelephonyManager::class.java) } doReturn mockTelephonyManager
67     }
68 
69     private val repository = NetworkScanRepository(context, SUB_ID)
70 
71     @Test
<lambda>null72     fun networkScanFlow_initial() = runBlocking {
73         val result = repository.networkScanFlow().firstWithTimeoutOrNull()
74 
75         assertThat(result).isNull()
76     }
77 
78     @Test
<lambda>null79     fun networkScanFlow_onResults(): Unit = runBlocking {
80         val cellInfos = listOf(CellInfoCdma().apply { cellIdentity = CELL_IDENTITY_CDMA })
81         val listDeferred = async {
82             repository.networkScanFlow().toListWithTimeout()
83         }
84         delay(100)
85 
86         callback?.onResults(cellInfos)
87 
88         assertThat(listDeferred.await()).containsExactly(
89             NetworkScanRepository.NetworkScanResult(
90                 state = NetworkScanRepository.NetworkScanState.ACTIVE,
91                 cellInfos = cellInfos,
92             )
93         )
94     }
95 
96     @Test
<lambda>null97     fun networkScanFlow_onComplete(): Unit = runBlocking {
98         val listDeferred = async {
99             repository.networkScanFlow().toListWithTimeout()
100         }
101         delay(100)
102 
103         callback?.onComplete()
104 
105         assertThat(listDeferred.await()).containsExactly(
106             NetworkScanRepository.NetworkScanResult(
107                 state = NetworkScanRepository.NetworkScanState.COMPLETE,
108                 cellInfos = emptyList(),
109             )
110         )
111     }
112 
113     @Test
<lambda>null114     fun networkScanFlow_onError(): Unit = runBlocking {
115         val listDeferred = async {
116             repository.networkScanFlow().toListWithTimeout()
117         }
118         delay(100)
119 
120         callback?.onError(1)
121 
122         assertThat(listDeferred.await()).containsExactly(
123             NetworkScanRepository.NetworkScanResult(
124                 state = NetworkScanRepository.NetworkScanState.ERROR,
125                 cellInfos = emptyList(),
126             )
127         )
128     }
129 
130     @Test
<lambda>null131     fun networkScanFlow_hasDuplicateItems(): Unit = runBlocking {
132         val cellInfos = listOf(
133             createCellInfoLte("123", false),
134             createCellInfoLte("123", false),
135             createCellInfoLte("124", true),
136             createCellInfoLte("124", true),
137             createCellInfoGsm("123", false),
138             createCellInfoGsm("123", false),
139         )
140         val listDeferred = async {
141             repository.networkScanFlow().toListWithTimeout()
142         }
143         delay(100)
144 
145         callback?.onResults(cellInfos)
146 
147         assertThat(listDeferred.await()).containsExactly(
148             NetworkScanRepository.NetworkScanResult(
149                 state = NetworkScanRepository.NetworkScanState.ACTIVE,
150                 cellInfos = listOf(
151                     createCellInfoLte("123", false),
152                     createCellInfoLte("124", true),
153                     createCellInfoGsm("123", false),
154                 ),
155             )
156         )
157     }
158 
159 
160     @Test
networkScanFlow_noDuplicateItemsnull161     fun networkScanFlow_noDuplicateItems(): Unit = runBlocking {
162         val cellInfos = listOf(
163             createCellInfoLte("123", false),
164             createCellInfoLte("123", true),
165             createCellInfoLte("124", false),
166             createCellInfoLte("124", true),
167             createCellInfoGsm("456", false),
168             createCellInfoGsm("456", true),
169         )
170         val listDeferred = async {
171             repository.networkScanFlow().toListWithTimeout()
172         }
173         delay(100)
174 
175         callback?.onResults(cellInfos)
176 
177         assertThat(listDeferred.await()).containsExactly(
178             NetworkScanRepository.NetworkScanResult(
179                 state = NetworkScanRepository.NetworkScanState.ACTIVE,
180                 cellInfos = listOf(
181                     createCellInfoLte("123", false),
182                     createCellInfoLte("123", true),
183                     createCellInfoLte("124", false),
184                     createCellInfoLte("124", true),
185                     createCellInfoGsm("456", false),
186                     createCellInfoGsm("456", true),
187                 )
188             )
189         )
190     }
191 
192     @Test
<lambda>null193     fun createNetworkScan_deviceHasNrSa_requestNgran(): Unit = runBlocking {
194         mockTelephonyManager.stub {
195             on { getAllowedNetworkTypesBitmask() } doReturn NETWORK_CLASS_BITMASK_5G
196             on { getPhoneCapability() } doReturn
197                 createPhoneCapability(intArrayOf(PhoneCapability.DEVICE_NR_CAPABILITY_SA))
198         }
199 
200         repository.networkScanFlow().firstWithTimeoutOrNull()
201 
202         verify(mockTelephonyManager).requestNetworkScan(argThat<NetworkScanRequest> {
203             specifiers.any { it.radioAccessNetwork == AccessNetworkType.NGRAN }
204         }, any(), any())
205     }
206 
207     @Test
<lambda>null208     fun createNetworkScan_deviceNoNrSa_noNgran(): Unit = runBlocking {
209         mockTelephonyManager.stub {
210             on { getAllowedNetworkTypesBitmask() } doReturn NETWORK_CLASS_BITMASK_5G
211             on { getPhoneCapability() } doReturn
212                 createPhoneCapability(intArrayOf(PhoneCapability.DEVICE_NR_CAPABILITY_NSA))
213         }
214 
215         repository.networkScanFlow().firstWithTimeoutOrNull()
216 
217         verify(mockTelephonyManager).requestNetworkScan(argThat<NetworkScanRequest> {
218             specifiers.none { it.radioAccessNetwork == AccessNetworkType.NGRAN }
219         }, any(), any())
220     }
221 
222     private companion object {
223         const val SUB_ID = 1
224         const val LONG = "Long"
225         const val SHORT = "Short"
226 
227         val CELL_IDENTITY_CDMA = CellIdentityCdma(
228             /* nid = */ 1,
229             /* sid = */ 2,
230             /* bid = */ 3,
231             /* lon = */ 4,
232             /* lat = */ 5,
233             /* alphal = */ LONG,
234             /* alphas = */ SHORT,
235         )
236 
createCellInfoLtenull237         private fun createCellInfoLte(alphaLong: String, registered: Boolean): CellInfoLte {
238             val cellIdentityLte = CellIdentityLte(
239                 /* ci = */ 1,
240                 /* pci = */ 2,
241                 /* tac = */ 3,
242                 /* earfcn = */ 4,
243                 /* bands = */ intArrayOf(1, 2),
244                 /* bandwidth = */ 10000,
245                 /* mccStr = */ null,
246                 /* mncStr = */ null,
247                 /* alphal = */ alphaLong,
248                 /* alphas = */ null,
249                 /* additionalPlmns = */ emptyList(),
250                 /* csgInfo = */ null,
251             )
252             return CellInfoLte().apply {
253                 cellIdentity = cellIdentityLte
254                 isRegistered = registered
255             }
256         }
257 
createCellInfoGsmnull258         private fun createCellInfoGsm(alphaLong: String, registered: Boolean): CellInfoGsm {
259             val cellIdentityGsm = CellIdentityGsm(
260                 /* lac = */ 1,
261                 /* cid = */ 2,
262                 /* arfcn = */ 3,
263                 /* bsic = */ 4,
264                 /* mccStr = */ "123",
265                 /* mncStr = */ "01",
266                 /* alphal = */ alphaLong,
267                 /* alphas = */ null,
268                 /* additionalPlmns = */ emptyList(),
269             )
270             return CellInfoGsm().apply {
271                 cellIdentity = cellIdentityGsm
272                 isRegistered = registered
273             }
274         }
275 
createPhoneCapabilitynull276         private fun createPhoneCapability(deviceNrCapabilities: IntArray) =
277             PhoneCapability.Builder().setDeviceNrCapabilities(deviceNrCapabilities).build()
278     }
279 }
280