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