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