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.CellInfo 22 import android.telephony.NetworkScanRequest 23 import android.telephony.PhoneCapability 24 import android.telephony.RadioAccessSpecifier 25 import android.telephony.TelephonyManager 26 import android.telephony.TelephonyScanManager 27 import android.util.Log 28 import androidx.lifecycle.LifecycleOwner 29 import com.android.settings.R 30 import com.android.settings.network.telephony.CellInfoUtil.getNetworkTitle 31 import com.android.settings.network.telephony.telephonyManager 32 import com.android.settingslib.spa.framework.util.collectLatestWithLifecycle 33 import kotlinx.coroutines.Dispatchers 34 import kotlinx.coroutines.asExecutor 35 import kotlinx.coroutines.channels.awaitClose 36 import kotlinx.coroutines.flow.Flow 37 import kotlinx.coroutines.flow.callbackFlow 38 import kotlinx.coroutines.flow.conflate 39 import kotlinx.coroutines.flow.flowOn 40 import kotlinx.coroutines.flow.onEach 41 42 class NetworkScanRepository(private val context: Context, subId: Int) { 43 enum class NetworkScanState { 44 ACTIVE, COMPLETE, ERROR 45 } 46 47 data class NetworkScanResult( 48 val state: NetworkScanState, 49 val cellInfos: List<CellInfo>, 50 ) 51 52 private val telephonyManager = context.telephonyManager(subId) 53 54 /** TODO: Move this to UI layer, when UI layer migrated to Kotlin. */ launchNetworkScannull55 fun launchNetworkScan(lifecycleOwner: LifecycleOwner, onResult: (NetworkScanResult) -> Unit) = 56 networkScanFlow().collectLatestWithLifecycle(lifecycleOwner, action = onResult) 57 58 data class CellInfoScanKey( 59 val title: String?, 60 val className: String, 61 val isRegistered: Boolean, 62 ) { 63 constructor(cellInfo: CellInfo) : this( 64 title = cellInfo.cellIdentity.getNetworkTitle(), 65 className = cellInfo.javaClass.name, 66 isRegistered = cellInfo.isRegistered, 67 ) 68 } 69 <lambda>null70 fun networkScanFlow(): Flow<NetworkScanResult> = callbackFlow { 71 var state = NetworkScanState.ACTIVE 72 var cellInfos: List<CellInfo> = emptyList() 73 74 val callback = object : TelephonyScanManager.NetworkScanCallback() { 75 override fun onResults(results: List<CellInfo>) { 76 cellInfos = results.distinctBy { CellInfoScanKey(it) } 77 sendResult() 78 } 79 80 override fun onComplete() { 81 state = NetworkScanState.COMPLETE 82 sendResult() 83 // Don't call close() here since onComplete() could happens before onResults() 84 } 85 86 override fun onError(error: Int) { 87 state = NetworkScanState.ERROR 88 sendResult() 89 close() 90 } 91 92 private fun sendResult() { 93 trySend(NetworkScanResult(state, cellInfos)) 94 } 95 } 96 97 val networkScan = telephonyManager.requestNetworkScan( 98 createNetworkScan(), 99 Dispatchers.Default.asExecutor(), 100 callback, 101 ) 102 103 awaitClose { 104 networkScan.stopScan() 105 Log.d(TAG, "network scan stopped") 106 } 107 }.conflate().onEach { Log.d(TAG, "networkScanFlow: $it") }.flowOn(Dispatchers.Default) 108 109 /** Create network scan for allowed network types. */ createNetworkScannull110 private fun createNetworkScan(): NetworkScanRequest { 111 val allowedNetworkTypes = getAllowedNetworkTypes() 112 Log.d(TAG, "createNetworkScan: allowedNetworkTypes = $allowedNetworkTypes") 113 val radioAccessSpecifiers = allowedNetworkTypes 114 .map { RadioAccessSpecifier(it, null, null) } 115 .toTypedArray() 116 return NetworkScanRequest( 117 NetworkScanRequest.SCAN_TYPE_ONE_SHOT, 118 radioAccessSpecifiers, 119 NetworkScanRequest.MIN_SEARCH_PERIODICITY_SEC, // one shot, not used 120 context.resources.getInteger(R.integer.config_network_scan_helper_max_search_time_sec), 121 true, 122 INCREMENTAL_RESULTS_PERIODICITY_SEC, 123 null, 124 ) 125 } 126 getAllowedNetworkTypesnull127 private fun getAllowedNetworkTypes(): List<Int> { 128 val networkTypeBitmap3gpp: Long = 129 telephonyManager.getAllowedNetworkTypesBitmask() and 130 TelephonyManager.NETWORK_STANDARDS_FAMILY_BITMASK_3GPP 131 return buildList { 132 // If the allowed network types are unknown or if they are of the right class, scan for 133 // them; otherwise, skip them to save scan time and prevent users from being shown 134 // networks that they can't connect to. 135 if (networkTypeBitmap3gpp == 0L 136 || networkTypeBitmap3gpp and TelephonyManager.NETWORK_CLASS_BITMASK_2G != 0L 137 ) { 138 add(AccessNetworkType.GERAN) 139 } 140 if (networkTypeBitmap3gpp == 0L 141 || networkTypeBitmap3gpp and TelephonyManager.NETWORK_CLASS_BITMASK_3G != 0L 142 ) { 143 add(AccessNetworkType.UTRAN) 144 } 145 if (networkTypeBitmap3gpp == 0L 146 || networkTypeBitmap3gpp and TelephonyManager.NETWORK_CLASS_BITMASK_4G != 0L 147 ) { 148 add(AccessNetworkType.EUTRAN) 149 } 150 // If a device supports 5G stand-alone then the code below should be re-enabled; however 151 // a device supporting only non-standalone mode cannot perform PLMN selection and camp 152 // on a 5G network, which means that it shouldn't scan for 5G at the expense of battery 153 // as part of the manual network selection process. 154 // 155 if (networkTypeBitmap3gpp == 0L 156 || (networkTypeBitmap3gpp and TelephonyManager.NETWORK_CLASS_BITMASK_5G != 0L && 157 hasNrSaCapability()) 158 ) { 159 add(AccessNetworkType.NGRAN) 160 Log.d(TAG, "radioAccessSpecifiers add NGRAN.") 161 } 162 } 163 } 164 hasNrSaCapabilitynull165 private fun hasNrSaCapability(): Boolean { 166 val phoneCapability = telephonyManager.getPhoneCapability() 167 return PhoneCapability.DEVICE_NR_CAPABILITY_SA in phoneCapability.deviceNrCapabilities 168 } 169 170 companion object { 171 private const val TAG = "NetworkScanRepository" 172 173 private const val INCREMENTAL_RESULTS_PERIODICITY_SEC = 3 174 } 175 } 176