1 /* 2 * Copyright (C) 2018 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; 18 19 import android.annotation.IntDef; 20 import android.content.Context; 21 import android.telephony.AccessNetworkConstants.AccessNetworkType; 22 import android.telephony.CellInfo; 23 import android.telephony.NetworkScan; 24 import android.telephony.NetworkScanRequest; 25 import android.telephony.PhoneCapability; 26 import android.telephony.RadioAccessSpecifier; 27 import android.telephony.TelephonyManager; 28 import android.telephony.TelephonyScanManager; 29 import android.util.Log; 30 31 import androidx.annotation.VisibleForTesting; 32 33 import com.android.internal.telephony.CellNetworkScanResult; 34 35 import com.android.settings.R; 36 37 import com.google.common.util.concurrent.FutureCallback; 38 import com.google.common.util.concurrent.Futures; 39 import com.google.common.util.concurrent.ListenableFuture; 40 import com.google.common.util.concurrent.MoreExecutors; 41 import com.google.common.util.concurrent.SettableFuture; 42 43 import java.lang.annotation.Retention; 44 import java.lang.annotation.RetentionPolicy; 45 import java.util.ArrayList; 46 import java.util.Arrays; 47 import java.util.List; 48 import java.util.concurrent.CancellationException; 49 import java.util.concurrent.Executor; 50 import java.util.stream.Collectors; 51 52 /** 53 * A helper class that builds the common interface and performs the network scan for two different 54 * network scan APIs. 55 */ 56 public class NetworkScanHelper { 57 public static final String TAG = "NetworkScanHelper"; 58 59 /** 60 * Callbacks interface to inform the network scan results. 61 */ 62 public interface NetworkScanCallback { 63 /** 64 * Called when the results is returned from {@link TelephonyManager}. This method will be 65 * called at least one time if there is no error occurred during the network scan. 66 * 67 * <p> This method can be called multiple times in one network scan, until 68 * {@link #onComplete()} or {@link #onError(int)} is called. 69 * 70 * @param results 71 */ onResults(List<CellInfo> results)72 void onResults(List<CellInfo> results); 73 74 /** 75 * Called when the current network scan process is finished. No more 76 * {@link #onResults(List)} will be called for the current network scan after this method is 77 * called. 78 */ onComplete()79 void onComplete(); 80 81 /** 82 * Called when an error occurred during the network scan process. 83 * 84 * <p> There is no more result returned from {@link TelephonyManager} if an error occurred. 85 * 86 * <p> {@link #onComplete()} will not be called if an error occurred. 87 * 88 * @see {@link NetworkScan.ScanErrorCode} 89 */ onError(int errorCode)90 void onError(int errorCode); 91 } 92 93 @Retention(RetentionPolicy.SOURCE) 94 @IntDef({NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS, NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS}) 95 public @interface NetworkQueryType {} 96 97 /** 98 * Performs the network scan using {@link TelephonyManager#getAvailableNetworks()}. The network 99 * scan results won't be returned to the caller until the network scan is completed. 100 * 101 * <p> This is typically used when the modem doesn't support the new network scan api 102 * {@link TelephonyManager#requestNetworkScan( 103 * NetworkScanRequest, Executor, TelephonyScanManager.NetworkScanCallback)}. 104 */ 105 public static final int NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS = 1; 106 107 /** 108 * Performs the network scan using {@link TelephonyManager#requestNetworkScan( 109 * NetworkScanRequest, Executor, TelephonyScanManager.NetworkScanCallback)} The network scan 110 * results will be returned to the caller periodically in a small time window until the network 111 * scan is completed. The complete results should be returned in the last called of 112 * {@link NetworkScanCallback#onResults(List)}. 113 * 114 * <p> This is recommended to be used if modem supports the new network scan api 115 * {@link TelephonyManager#requestNetworkScan( 116 * NetworkScanRequest, Executor, TelephonyScanManager.NetworkScanCallback)} 117 */ 118 public static final int NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS = 2; 119 120 /** The constants below are used in the async network scan. */ 121 @VisibleForTesting 122 static final boolean INCREMENTAL_RESULTS = true; 123 @VisibleForTesting 124 static final int SEARCH_PERIODICITY_SEC = 5; 125 @VisibleForTesting 126 static final int MAX_SEARCH_TIME_SEC = 300; 127 @VisibleForTesting 128 static final int INCREMENTAL_RESULTS_PERIODICITY_SEC = 3; 129 130 private final NetworkScanCallback mNetworkScanCallback; 131 private final TelephonyManager mTelephonyManager; 132 private final TelephonyScanManager.NetworkScanCallback mInternalNetworkScanCallback; 133 private final Executor mExecutor; 134 135 private int mMaxSearchTimeSec = MAX_SEARCH_TIME_SEC; 136 private NetworkScan mNetworkScanRequester; 137 138 /** Callbacks for sync network scan */ 139 private ListenableFuture<List<CellInfo>> mNetworkScanFuture; 140 NetworkScanHelper(TelephonyManager tm, NetworkScanCallback callback, Executor executor)141 public NetworkScanHelper(TelephonyManager tm, NetworkScanCallback callback, Executor executor) { 142 mTelephonyManager = tm; 143 mNetworkScanCallback = callback; 144 mInternalNetworkScanCallback = new NetworkScanCallbackImpl(); 145 mExecutor = executor; 146 } 147 NetworkScanHelper(Context context, TelephonyManager tm, NetworkScanCallback callback, Executor executor)148 public NetworkScanHelper(Context context, TelephonyManager tm, NetworkScanCallback callback, 149 Executor executor) { 150 this(tm, callback, executor); 151 mMaxSearchTimeSec = context.getResources().getInteger( 152 R.integer.config_network_scan_helper_max_search_time_sec); 153 } 154 155 @VisibleForTesting createNetworkScanForPreferredAccessNetworks()156 NetworkScanRequest createNetworkScanForPreferredAccessNetworks() { 157 long networkTypeBitmap3gpp = mTelephonyManager.getPreferredNetworkTypeBitmask() 158 & TelephonyManager.NETWORK_STANDARDS_FAMILY_BITMASK_3GPP; 159 160 List<RadioAccessSpecifier> radioAccessSpecifiers = new ArrayList<>(); 161 // If the allowed network types are unknown or if they are of the right class, scan for 162 // them; otherwise, skip them to save scan time and prevent users from being shown networks 163 // that they can't connect to. 164 if (networkTypeBitmap3gpp == 0 165 || (networkTypeBitmap3gpp & TelephonyManager.NETWORK_CLASS_BITMASK_2G) != 0) { 166 radioAccessSpecifiers.add( 167 new RadioAccessSpecifier(AccessNetworkType.GERAN, null, null)); 168 } 169 if (networkTypeBitmap3gpp == 0 170 || (networkTypeBitmap3gpp & TelephonyManager.NETWORK_CLASS_BITMASK_3G) != 0) { 171 radioAccessSpecifiers.add( 172 new RadioAccessSpecifier(AccessNetworkType.UTRAN, null, null)); 173 } 174 if (networkTypeBitmap3gpp == 0 175 || (networkTypeBitmap3gpp & TelephonyManager.NETWORK_CLASS_BITMASK_4G) != 0) { 176 radioAccessSpecifiers.add( 177 new RadioAccessSpecifier(AccessNetworkType.EUTRAN, null, null)); 178 } 179 // If a device supports 5G stand-alone then the code below should be re-enabled; however 180 // a device supporting only non-standalone mode cannot perform PLMN selection and camp on 181 // a 5G network, which means that it shouldn't scan for 5G at the expense of battery as 182 // part of the manual network selection process. 183 // 184 if (networkTypeBitmap3gpp == 0 185 || (hasNrSaCapability() 186 && (networkTypeBitmap3gpp & TelephonyManager.NETWORK_CLASS_BITMASK_5G) != 0)) { 187 radioAccessSpecifiers.add( 188 new RadioAccessSpecifier(AccessNetworkType.NGRAN, null, null)); 189 Log.d(TAG, "radioAccessSpecifiers add NGRAN."); 190 } 191 192 return new NetworkScanRequest( 193 NetworkScanRequest.SCAN_TYPE_ONE_SHOT, 194 radioAccessSpecifiers.toArray( 195 new RadioAccessSpecifier[radioAccessSpecifiers.size()]), 196 SEARCH_PERIODICITY_SEC, 197 mMaxSearchTimeSec, 198 INCREMENTAL_RESULTS, 199 INCREMENTAL_RESULTS_PERIODICITY_SEC, 200 null /* List of PLMN ids (MCC-MNC) */); 201 } 202 203 /** 204 * Performs a network scan for the given type {@code type}. 205 * {@link #NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS} is recommended if modem supports 206 * {@link TelephonyManager#requestNetworkScan( 207 * NetworkScanRequest, Executor, TelephonyScanManager.NetworkScanCallback)}. 208 * 209 * @param type used to tell which network scan API should be used. 210 */ startNetworkScan(@etworkQueryType int type)211 public void startNetworkScan(@NetworkQueryType int type) { 212 if (type == NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS) { 213 mNetworkScanFuture = SettableFuture.create(); 214 Futures.addCallback(mNetworkScanFuture, new FutureCallback<List<CellInfo>>() { 215 @Override 216 public void onSuccess(List<CellInfo> result) { 217 onResults(result); 218 onComplete(); 219 } 220 221 @Override 222 public void onFailure(Throwable t) { 223 if (t instanceof CancellationException) { 224 return; 225 } 226 int errCode = Integer.parseInt(t.getMessage()); 227 onError(errCode); 228 } 229 }, MoreExecutors.directExecutor()); 230 mExecutor.execute(new NetworkScanSyncTask( 231 mTelephonyManager, (SettableFuture) mNetworkScanFuture)); 232 } else if (type == NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS) { 233 if (mNetworkScanRequester != null) { 234 return; 235 } 236 mNetworkScanRequester = mTelephonyManager.requestNetworkScan( 237 createNetworkScanForPreferredAccessNetworks(), 238 mExecutor, 239 mInternalNetworkScanCallback); 240 if (mNetworkScanRequester == null) { 241 onError(NetworkScan.ERROR_RADIO_INTERFACE_ERROR); 242 } 243 } 244 } 245 246 /** 247 * The network scan of type {@link #NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS} can't be stopped, 248 * however, the result of the current network scan won't be returned to the callback after 249 * calling this method. 250 */ stopNetworkQuery()251 public void stopNetworkQuery() { 252 if (mNetworkScanRequester != null) { 253 mNetworkScanRequester.stopScan(); 254 mNetworkScanRequester = null; 255 } 256 257 if (mNetworkScanFuture != null) { 258 mNetworkScanFuture.cancel(true /* mayInterruptIfRunning */); 259 mNetworkScanFuture = null; 260 } 261 } 262 onResults(List<CellInfo> cellInfos)263 private void onResults(List<CellInfo> cellInfos) { 264 mNetworkScanCallback.onResults(cellInfos); 265 } 266 onComplete()267 private void onComplete() { 268 mNetworkScanCallback.onComplete(); 269 } 270 onError(int errCode)271 private void onError(int errCode) { 272 mNetworkScanCallback.onError(errCode); 273 } 274 hasNrSaCapability()275 private boolean hasNrSaCapability() { 276 return Arrays.stream( 277 mTelephonyManager.getPhoneCapability().getDeviceNrCapabilities()) 278 .anyMatch(i -> i == PhoneCapability.DEVICE_NR_CAPABILITY_SA); 279 } 280 281 /** 282 * Converts the status code of {@link CellNetworkScanResult} to one of the 283 * {@link NetworkScan.ScanErrorCode}. 284 * @param errCode status code from {@link CellNetworkScanResult}. 285 * 286 * @return one of the scan error code from {@link NetworkScan.ScanErrorCode}. 287 */ convertToScanErrorCode(int errCode)288 private static int convertToScanErrorCode(int errCode) { 289 switch (errCode) { 290 case CellNetworkScanResult.STATUS_RADIO_NOT_AVAILABLE: 291 return NetworkScan.ERROR_RADIO_INTERFACE_ERROR; 292 case CellNetworkScanResult.STATUS_RADIO_GENERIC_FAILURE: 293 default: 294 return NetworkScan.ERROR_MODEM_ERROR; 295 } 296 } 297 298 private final class NetworkScanCallbackImpl extends TelephonyScanManager.NetworkScanCallback { onResults(List<CellInfo> results)299 public void onResults(List<CellInfo> results) { 300 Log.d(TAG, "Async scan onResults() results = " 301 + CellInfoUtil.cellInfoListToString(results)); 302 NetworkScanHelper.this.onResults(results); 303 } 304 onComplete()305 public void onComplete() { 306 Log.d(TAG, "async scan onComplete()"); 307 NetworkScanHelper.this.onComplete(); 308 } 309 onError(@etworkScan.ScanErrorCode int errCode)310 public void onError(@NetworkScan.ScanErrorCode int errCode) { 311 Log.d(TAG, "async scan onError() errorCode = " + errCode); 312 NetworkScanHelper.this.onError(errCode); 313 } 314 } 315 316 private static final class NetworkScanSyncTask implements Runnable { 317 private final SettableFuture<List<CellInfo>> mCallback; 318 private final TelephonyManager mTelephonyManager; 319 NetworkScanSyncTask( TelephonyManager telephonyManager, SettableFuture<List<CellInfo>> callback)320 NetworkScanSyncTask( 321 TelephonyManager telephonyManager, SettableFuture<List<CellInfo>> callback) { 322 mTelephonyManager = telephonyManager; 323 mCallback = callback; 324 } 325 326 @Override run()327 public void run() { 328 final CellNetworkScanResult result = mTelephonyManager.getAvailableNetworks(); 329 if (result.getStatus() == CellNetworkScanResult.STATUS_SUCCESS) { 330 final List<CellInfo> cellInfos = result.getOperators() 331 .stream() 332 .map(operatorInfo 333 -> CellInfoUtil.convertOperatorInfoToCellInfo(operatorInfo)) 334 .collect(Collectors.toList()); 335 Log.d(TAG, "Sync network scan completed, cellInfos = " 336 + CellInfoUtil.cellInfoListToString(cellInfos)); 337 mCallback.set(cellInfos); 338 } else { 339 final Throwable error = new Throwable( 340 Integer.toString(convertToScanErrorCode(result.getStatus()))); 341 mCallback.setException(error); 342 Log.d(TAG, "Sync network scan error, ex = " + error); 343 } 344 } 345 } 346 } 347