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.ons; 18 19 import android.content.Context; 20 import android.os.Handler; 21 import android.os.HandlerThread; 22 import android.os.Message; 23 import android.os.PersistableBundle; 24 import android.telephony.AccessNetworkConstants; 25 import android.telephony.AvailableNetworkInfo; 26 import android.telephony.CarrierConfigManager; 27 import android.telephony.CellInfo; 28 import android.telephony.CellInfoLte; 29 import android.telephony.NetworkScan; 30 import android.telephony.NetworkScanRequest; 31 import android.telephony.RadioAccessSpecifier; 32 import android.telephony.TelephonyManager; 33 import android.telephony.TelephonyScanManager; 34 import android.util.ArraySet; 35 36 import com.android.internal.annotations.VisibleForTesting; 37 import com.android.telephony.Rlog; 38 39 import java.util.ArrayList; 40 import java.util.List; 41 import java.util.Set; 42 import java.util.concurrent.TimeUnit; 43 44 /** 45 * Network Scan controller class which will scan for the specific bands as requested and 46 * provide results to caller when ready. 47 */ 48 public class ONSNetworkScanCtlr { 49 private static final String LOG_TAG = "ONSNetworkScanCtlr"; 50 private static final boolean DBG = true; 51 private static final int SEARCH_PERIODICITY_SLOW = (int) TimeUnit.MINUTES.toSeconds(5); 52 private static final int SEARCH_PERIODICITY_FAST = (int) TimeUnit.MINUTES.toSeconds(1); 53 private static final int MAX_SEARCH_TIME = (int) TimeUnit.MINUTES.toSeconds(1); 54 private static final int SCAN_RESTART_TIME = (int) TimeUnit.MINUTES.toMillis(1); 55 private final Object mLock = new Object(); 56 57 /* message to handle scan responses from modem */ 58 private static final int MSG_SCAN_RESULTS_AVAILABLE = 1; 59 private static final int MSG_SCAN_COMPLETE = 2; 60 private static final int MSG_SCAN_ERROR = 3; 61 62 /* scan object to keep track of current scan request */ 63 private NetworkScan mCurrentScan; 64 private boolean mIsScanActive; 65 private NetworkScanRequest mCurrentScanRequest; 66 private List<String> mMccMncs; 67 private TelephonyManager mTelephonyManager; 68 private CarrierConfigManager configManager; 69 private int mRsrpEntryThreshold; 70 @VisibleForTesting 71 protected NetworkAvailableCallBack mNetworkAvailableCallBack; 72 HandlerThread mThread; 73 private Handler mHandler; 74 75 @VisibleForTesting 76 public TelephonyScanManager.NetworkScanCallback mNetworkScanCallback = 77 new TelephonyScanManager.NetworkScanCallback() { 78 79 @Override 80 public void onResults(List<CellInfo> results) { 81 logDebug("Total results :" + results.size()); 82 for (CellInfo cellInfo : results) { 83 logDebug("cell info: " + cellInfo); 84 } 85 86 Message message = Message.obtain(mHandler, MSG_SCAN_RESULTS_AVAILABLE, results); 87 message.sendToTarget(); 88 } 89 90 @Override 91 public void onComplete() { 92 logDebug("Scan completed!"); 93 Message message = Message.obtain(mHandler, MSG_SCAN_COMPLETE, NetworkScan.SUCCESS); 94 mHandler.sendMessageDelayed(message, SCAN_RESTART_TIME); 95 } 96 97 @Override 98 public void onError(@NetworkScan.ScanErrorCode int error) { 99 logDebug("Scan error " + error); 100 Message message = Message.obtain(mHandler, MSG_SCAN_ERROR, error); 101 message.sendToTarget(); 102 } 103 }; 104 105 /** 106 * call back for network availability 107 */ 108 public interface NetworkAvailableCallBack { 109 110 /** 111 * Returns the scan results to the user, this callback will be called multiple times. 112 */ onNetworkAvailability(List<CellInfo> results)113 void onNetworkAvailability(List<CellInfo> results); 114 115 /** 116 * on error 117 * @param error 118 */ onError(int error)119 void onError(int error); 120 } 121 getIntCarrierConfig(String key)122 private int getIntCarrierConfig(String key) { 123 PersistableBundle b = null; 124 if (configManager != null) { 125 // If an invalid subId is used, this bundle will contain default values. 126 b = configManager.getConfig(); 127 } 128 if (b != null) { 129 return b.getInt(key); 130 } else { 131 // Return static default defined in CarrierConfigManager. 132 return CarrierConfigManager.getDefaultConfig().getInt(key); 133 } 134 } 135 136 /** 137 * analyze scan results 138 * @param results contains all available cells matching the scan request at current location. 139 */ analyzeScanResults(List<CellInfo> results)140 public void analyzeScanResults(List<CellInfo> results) { 141 /* Inform registrants about availability of network */ 142 if (!mIsScanActive || results == null) { 143 return; 144 } 145 List<CellInfo> filteredResults = new ArrayList<CellInfo>(); 146 synchronized (mLock) { 147 for (CellInfo cellInfo : results) { 148 if (mMccMncs.contains(getMccMnc(cellInfo))) { 149 if (cellInfo instanceof CellInfoLte) { 150 int rsrp = ((CellInfoLte) cellInfo).getCellSignalStrength().getRsrp(); 151 logDebug("cell info rsrp: " + rsrp); 152 if (rsrp >= mRsrpEntryThreshold) { 153 filteredResults.add(cellInfo); 154 } 155 } 156 } 157 } 158 } 159 if ((filteredResults.size() >= 1) && (mNetworkAvailableCallBack != null)) { 160 /* Todo: change to aggregate results on success. */ 161 mNetworkAvailableCallBack.onNetworkAvailability(filteredResults); 162 } 163 } 164 invalidateScanOnError(int error)165 private void invalidateScanOnError(int error) { 166 logDebug("scan invalidated on error"); 167 if (mNetworkAvailableCallBack != null) { 168 mNetworkAvailableCallBack.onError(error); 169 } 170 171 synchronized (mLock) { 172 mIsScanActive = false; 173 mCurrentScan = null; 174 } 175 } 176 ONSNetworkScanCtlr(Context c, TelephonyManager telephonyManager, NetworkAvailableCallBack networkAvailableCallBack)177 public ONSNetworkScanCtlr(Context c, TelephonyManager telephonyManager, 178 NetworkAvailableCallBack networkAvailableCallBack) { 179 init(c, telephonyManager, networkAvailableCallBack); 180 } 181 182 /** 183 * initialize Network Scan controller 184 * @param c context 185 * @param telephonyManager Telephony manager instance 186 * @param networkAvailableCallBack callback to be called when network selection is done 187 */ init(Context context, TelephonyManager telephonyManager, NetworkAvailableCallBack networkAvailableCallBack)188 public void init(Context context, TelephonyManager telephonyManager, 189 NetworkAvailableCallBack networkAvailableCallBack) { 190 log("init called"); 191 mThread = new HandlerThread(LOG_TAG); 192 mThread.start(); 193 mHandler = new Handler(mThread.getLooper()) { 194 @Override 195 public void handleMessage(Message msg) { 196 switch (msg.what) { 197 case MSG_SCAN_RESULTS_AVAILABLE: 198 logDebug("Msg received for scan results"); 199 /* Todo: need to aggregate the results */ 200 analyzeScanResults((List<CellInfo>) msg.obj); 201 break; 202 case MSG_SCAN_COMPLETE: 203 logDebug("Msg received for scan complete"); 204 restartScan(); 205 break; 206 case MSG_SCAN_ERROR: 207 logDebug("Msg received for scan error"); 208 invalidateScanOnError((int) msg.obj); 209 break; 210 default: 211 log("invalid message"); 212 break; 213 } 214 } 215 }; 216 mTelephonyManager = telephonyManager; 217 mNetworkAvailableCallBack = networkAvailableCallBack; 218 configManager = (CarrierConfigManager) context.getSystemService( 219 Context.CARRIER_CONFIG_SERVICE); 220 } 221 222 /* get mcc mnc from cell info if the cell is for LTE */ getMccMnc(CellInfo cellInfo)223 private String getMccMnc(CellInfo cellInfo) { 224 if (cellInfo instanceof CellInfoLte) { 225 return ((CellInfoLte) cellInfo).getCellIdentity().getMccString() 226 + ((CellInfoLte) cellInfo).getCellIdentity().getMncString(); 227 } 228 229 return null; 230 } 231 createNetworkScanRequest(ArrayList<AvailableNetworkInfo> availableNetworks, int periodicity)232 private NetworkScanRequest createNetworkScanRequest(ArrayList<AvailableNetworkInfo> availableNetworks, 233 int periodicity) { 234 RadioAccessSpecifier[] ras = new RadioAccessSpecifier[1]; 235 ArrayList<String> mccMncs = new ArrayList<String>(); 236 Set<Integer> bandSet = new ArraySet<>(); 237 238 /* by default add band 48 */ 239 bandSet.add(AccessNetworkConstants.EutranBand.BAND_48); 240 /* retrieve mcc mncs and bands for available networks */ 241 for (AvailableNetworkInfo availableNetwork : availableNetworks) { 242 mccMncs.addAll(availableNetwork.getMccMncs()); 243 bandSet.addAll(availableNetwork.getBands()); 244 } 245 246 int[] bands = bandSet.stream().mapToInt(band->band).toArray(); 247 /* create network scan request */ 248 ras[0] = new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.EUTRAN, bands, 249 null); 250 NetworkScanRequest networkScanRequest = new NetworkScanRequest( 251 NetworkScanRequest.SCAN_TYPE_PERIODIC, ras, periodicity, MAX_SEARCH_TIME, false, 252 NetworkScanRequest.MAX_INCREMENTAL_PERIODICITY_SEC, mccMncs); 253 synchronized (mLock) { 254 mMccMncs = mccMncs; 255 } 256 return networkScanRequest; 257 } 258 259 /** 260 * start less interval network scan 261 * @param availableNetworks list of subscriptions for which the scanning needs to be started. 262 * @return true if successfully accepted request. 263 */ startFastNetworkScan(ArrayList<AvailableNetworkInfo> availableNetworks)264 public boolean startFastNetworkScan(ArrayList<AvailableNetworkInfo> availableNetworks) { 265 NetworkScanRequest networkScanRequest = createNetworkScanRequest(availableNetworks, 266 SEARCH_PERIODICITY_FAST); 267 return startNetworkScan(networkScanRequest); 268 } 269 270 startNetworkScan(NetworkScanRequest networkScanRequest)271 private boolean startNetworkScan(NetworkScanRequest networkScanRequest) { 272 NetworkScan networkScan; 273 synchronized (mLock) { 274 /* if the request is same as existing one, then make sure to not proceed */ 275 if (mIsScanActive && mCurrentScanRequest.equals(networkScanRequest)) { 276 return true; 277 } 278 279 /* Need to stop current scan if we already have one */ 280 stopNetworkScan(); 281 282 /* user lower threshold to enable modem stack */ 283 mRsrpEntryThreshold = 284 getIntCarrierConfig( 285 CarrierConfigManager.KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSRP_INT); 286 287 /* start new scan */ 288 networkScan = mTelephonyManager.requestNetworkScan(networkScanRequest, 289 mNetworkScanCallback); 290 291 mCurrentScan = networkScan; 292 mIsScanActive = true; 293 mCurrentScanRequest = networkScanRequest; 294 } 295 296 logDebug("startNetworkScan " + networkScanRequest); 297 return true; 298 } 299 restartScan()300 private void restartScan() { 301 NetworkScan networkScan; 302 logDebug("restartScan"); 303 synchronized (mLock) { 304 if (mCurrentScanRequest != null) { 305 networkScan = mTelephonyManager.requestNetworkScan(mCurrentScanRequest, 306 mNetworkScanCallback); 307 mIsScanActive = true; 308 } 309 } 310 } 311 312 /** 313 * stop network scan 314 */ stopNetworkScan()315 public void stopNetworkScan() { 316 logDebug("stopNetworkScan"); 317 synchronized (mLock) { 318 if (mIsScanActive && mCurrentScan != null) { 319 try { 320 mCurrentScan.stopScan(); 321 } catch (IllegalArgumentException iae) { 322 logDebug("Scan failed with exception " + iae); 323 } 324 mIsScanActive = false; 325 mCurrentScan = null; 326 mCurrentScanRequest = null; 327 } 328 } 329 } 330 log(String msg)331 private static void log(String msg) { 332 Rlog.d(LOG_TAG, msg); 333 } 334 logDebug(String msg)335 private static void logDebug(String msg) { 336 if (DBG) { 337 Rlog.d(LOG_TAG, msg); 338 } 339 } 340 } 341