• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 java.util.concurrent.Executors
34 import kotlinx.coroutines.Dispatchers
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             // requestNetworkScan() could call callbacks concurrently, so we use a single thread
100             // to avoid racing conditions.
101             Executors.newSingleThreadExecutor(),
102             callback,
103         )
104 
105         awaitClose {
106             networkScan.stopScan()
107             Log.d(TAG, "network scan stopped")
108         }
109     }.conflate().onEach { Log.d(TAG, "networkScanFlow: $it") }.flowOn(Dispatchers.Default)
110 
111     /** Create network scan for allowed network types. */
createNetworkScannull112     private fun createNetworkScan(): NetworkScanRequest {
113         val allowedNetworkTypes = getAllowedNetworkTypes()
114         Log.d(TAG, "createNetworkScan: allowedNetworkTypes = $allowedNetworkTypes")
115         val radioAccessSpecifiers = allowedNetworkTypes
116             .map { RadioAccessSpecifier(it, null, null) }
117             .toTypedArray()
118         return NetworkScanRequest(
119             NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
120             radioAccessSpecifiers,
121             NetworkScanRequest.MIN_SEARCH_PERIODICITY_SEC, // one shot, not used
122             context.resources.getInteger(R.integer.config_network_scan_helper_max_search_time_sec),
123             true,
124             INCREMENTAL_RESULTS_PERIODICITY_SEC,
125             null,
126         )
127     }
128 
getAllowedNetworkTypesnull129     private fun getAllowedNetworkTypes(): List<Int> {
130         val networkTypeBitmap3gpp: Long =
131             telephonyManager.getAllowedNetworkTypesBitmask() and
132                 TelephonyManager.NETWORK_STANDARDS_FAMILY_BITMASK_3GPP
133         return buildList {
134             // If the allowed network types are unknown or if they are of the right class, scan for
135             // them; otherwise, skip them to save scan time and prevent users from being shown
136             // networks that they can't connect to.
137             if (networkTypeBitmap3gpp == 0L
138                 || networkTypeBitmap3gpp and TelephonyManager.NETWORK_CLASS_BITMASK_2G != 0L
139             ) {
140                 add(AccessNetworkType.GERAN)
141             }
142             if (networkTypeBitmap3gpp == 0L
143                 || networkTypeBitmap3gpp and TelephonyManager.NETWORK_CLASS_BITMASK_3G != 0L
144             ) {
145                 add(AccessNetworkType.UTRAN)
146             }
147             if (networkTypeBitmap3gpp == 0L
148                 || networkTypeBitmap3gpp and TelephonyManager.NETWORK_CLASS_BITMASK_4G != 0L
149             ) {
150                 add(AccessNetworkType.EUTRAN)
151             }
152             // If a device supports 5G stand-alone then the code below should be re-enabled; however
153             // a device supporting only non-standalone mode cannot perform PLMN selection and camp
154             // on a 5G network, which means that it shouldn't scan for 5G at the expense of battery
155             // as part of the manual network selection process.
156             //
157             if (networkTypeBitmap3gpp == 0L
158                 || (networkTypeBitmap3gpp and TelephonyManager.NETWORK_CLASS_BITMASK_5G != 0L &&
159                     hasNrSaCapability())
160             ) {
161                 add(AccessNetworkType.NGRAN)
162                 Log.d(TAG, "radioAccessSpecifiers add NGRAN.")
163             }
164         }
165     }
166 
hasNrSaCapabilitynull167     private fun hasNrSaCapability(): Boolean {
168         val phoneCapability = telephonyManager.getPhoneCapability()
169         return PhoneCapability.DEVICE_NR_CAPABILITY_SA in phoneCapability.deviceNrCapabilities
170     }
171 
172     companion object {
173         private const val TAG = "NetworkScanRepository"
174 
175         private const val INCREMENTAL_RESULTS_PERIODICITY_SEC = 3
176     }
177 }
178