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