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.CellInfoNr;
30 import android.telephony.CellSignalStrengthNr;
31 import android.telephony.NetworkScan;
32 import android.telephony.NetworkScanRequest;
33 import android.telephony.RadioAccessSpecifier;
34 import android.telephony.TelephonyManager;
35 import android.telephony.TelephonyScanManager;
36 import android.util.ArraySet;
37 
38 import com.android.internal.annotations.VisibleForTesting;
39 import com.android.telephony.Rlog;
40 
41 import java.util.ArrayList;
42 import java.util.Arrays;
43 import java.util.stream.Collectors;
44 import java.util.List;
45 import java.util.Set;
46 import java.util.concurrent.TimeUnit;
47 
48 /**
49  * Network Scan controller class which will scan for the specific bands as requested and
50  * provide results to caller when ready.
51  */
52 public class ONSNetworkScanCtlr {
53     private static final String LOG_TAG = "ONSNetworkScanCtlr";
54     private static final boolean DBG = true;
55     private static final int SEARCH_PERIODICITY_SLOW = (int) TimeUnit.MINUTES.toSeconds(5);
56     private static final int SEARCH_PERIODICITY_FAST = (int) TimeUnit.MINUTES.toSeconds(1);
57     private static final int MAX_SEARCH_TIME = (int) TimeUnit.MINUTES.toSeconds(1);
58     private static final int SCAN_RESTART_TIME = (int) TimeUnit.MINUTES.toMillis(1);
59     private final Object mLock = new Object();
60 
61     /* message  to handle scan responses from modem */
62     private static final int MSG_SCAN_RESULTS_AVAILABLE = 1;
63     private static final int MSG_SCAN_COMPLETE = 2;
64     private static final int MSG_SCAN_ERROR = 3;
65 
66     private Boolean mIs4gScanEnabled = null;
67 
68     @VisibleForTesting
69     static final RadioAccessSpecifier DEFAULT_5G_RAS = new RadioAccessSpecifier(
70             AccessNetworkConstants.AccessNetworkType.NGRAN,
71             new int[] {
72                 AccessNetworkConstants.NgranBands.BAND_48,
73                 AccessNetworkConstants.NgranBands.BAND_71},
74             null);
75     @VisibleForTesting
76     static final RadioAccessSpecifier DEFAULT_4G_RAS = new RadioAccessSpecifier(
77         AccessNetworkConstants.AccessNetworkType.NGRAN,
78         new int[] {
79                 AccessNetworkConstants.EutranBand.BAND_48,
80                 AccessNetworkConstants.EutranBand.BAND_71},
81         null);
82 
83     /* scan object to keep track of current scan request */
84     private NetworkScan mCurrentScan;
85     private boolean mIsScanActive;
86     private NetworkScanRequest mCurrentScanRequest;
87     private List<String> mMccMncs;
88     private TelephonyManager mTelephonyManager;
89     private CarrierConfigManager configManager;
90     private int mRsrpEntryThreshold;
91     private int mSsRsrpEntryThreshold;
92     @VisibleForTesting
93     protected NetworkAvailableCallBack mNetworkAvailableCallBack;
94     HandlerThread mThread;
95     private Handler mHandler;
96 
97     @VisibleForTesting
98     public TelephonyScanManager.NetworkScanCallback mNetworkScanCallback =
99             new TelephonyScanManager.NetworkScanCallback() {
100 
101         @Override
102         public void onResults(List<CellInfo> results) {
103             logDebug("Total results :" + results.size());
104             for (CellInfo cellInfo : results) {
105                 logDebug("cell info: " + cellInfo);
106             }
107 
108             Message message = Message.obtain(mHandler, MSG_SCAN_RESULTS_AVAILABLE, results);
109             message.sendToTarget();
110         }
111 
112         @Override
113         public void onComplete() {
114             logDebug("Scan completed!");
115             Message message = Message.obtain(mHandler, MSG_SCAN_COMPLETE, NetworkScan.SUCCESS);
116             mHandler.sendMessageDelayed(message, SCAN_RESTART_TIME);
117         }
118 
119         @Override
120         public void onError(@NetworkScan.ScanErrorCode int error) {
121             logDebug("Scan error " + error);
122             Message message = Message.obtain(mHandler, MSG_SCAN_ERROR, error);
123             message.sendToTarget();
124         }
125     };
126 
127     /**
128      * call back for network availability
129      */
130     public interface NetworkAvailableCallBack {
131 
132         /**
133          * Returns the scan results to the user, this callback will be called multiple times.
134          */
onNetworkAvailability(List<CellInfo> results)135         void onNetworkAvailability(List<CellInfo> results);
136 
137         /**
138          * on error
139          * @param error
140          */
onError(int error)141         void onError(int error);
142     }
143 
getConfigBundle()144     private PersistableBundle getConfigBundle() {
145         if (configManager != null) {
146             // If an invalid subId is used, this bundle will contain default values.
147             return configManager.getConfig();
148         }
149         return null;
150     }
151 
getIntCarrierConfig(String key)152     private int getIntCarrierConfig(String key) {
153         PersistableBundle b = getConfigBundle();
154         if (b != null) {
155             return b.getInt(key);
156         } else {
157             // Return static default defined in CarrierConfigManager.
158             return CarrierConfigManager.getDefaultConfig().getInt(key);
159         }
160     }
161 
getBooleanCarrierConfig(String key)162     private boolean getBooleanCarrierConfig(String key) {
163         PersistableBundle b = getConfigBundle();
164         if (b != null) {
165             return b.getBoolean(key);
166         } else {
167             // Return static default defined in CarrierConfigManager.
168             return CarrierConfigManager.getDefaultConfig().getBoolean(key);
169         }
170     }
171 
172     /**
173      * analyze scan results
174      * @param results contains all available cells matching the scan request at current location.
175      */
analyzeScanResults(List<CellInfo> results)176     public void analyzeScanResults(List<CellInfo> results) {
177         /* Inform registrants about availability of network */
178         if (!mIsScanActive || results == null) {
179           return;
180         }
181         List<CellInfo> filteredResults = new ArrayList<CellInfo>();
182         mIs4gScanEnabled = getIs4gScanEnabled();
183         synchronized (mLock) {
184             for (CellInfo cellInfo : results) {
185                 if (mMccMncs.contains(getMccMnc(cellInfo))) {
186                     if (cellInfo instanceof CellInfoNr) {
187                         CellInfoNr nrCellInfo = (CellInfoNr) cellInfo;
188                         int ssRsrp = ((CellSignalStrengthNr) nrCellInfo.getCellSignalStrength())
189                                 .getSsRsrp();
190                         logDebug("cell info ssRsrp: " + ssRsrp);
191                         if (ssRsrp >= mSsRsrpEntryThreshold) {
192                             filteredResults.add(cellInfo);
193                         }
194                     }
195                     if (mIs4gScanEnabled && cellInfo instanceof CellInfoLte) {
196                         int rsrp = ((CellInfoLte) cellInfo).getCellSignalStrength().getRsrp();
197                         logDebug("cell info rsrp: " + rsrp);
198                         if (rsrp >= mRsrpEntryThreshold) {
199                             filteredResults.add(cellInfo);
200                         }
201                     }
202                 }
203             }
204         }
205         if ((filteredResults.size() >= 1) && (mNetworkAvailableCallBack != null)) {
206             /* Todo: change to aggregate results on success. */
207             mNetworkAvailableCallBack.onNetworkAvailability(filteredResults);
208         }
209     }
210 
invalidateScanOnError(int error)211     private void invalidateScanOnError(int error) {
212         logDebug("scan invalidated on error");
213         if (mNetworkAvailableCallBack != null) {
214             mNetworkAvailableCallBack.onError(error);
215         }
216 
217         synchronized (mLock) {
218             mIsScanActive = false;
219             mCurrentScan = null;
220         }
221     }
222 
ONSNetworkScanCtlr(Context c, TelephonyManager telephonyManager, NetworkAvailableCallBack networkAvailableCallBack)223     public ONSNetworkScanCtlr(Context c, TelephonyManager telephonyManager,
224             NetworkAvailableCallBack networkAvailableCallBack) {
225         init(c, telephonyManager, networkAvailableCallBack);
226     }
227 
228     /**
229      * initialize Network Scan controller
230      * @param c context
231      * @param telephonyManager Telephony manager instance
232      * @param networkAvailableCallBack callback to be called when network selection is done
233      */
init(Context context, TelephonyManager telephonyManager, NetworkAvailableCallBack networkAvailableCallBack)234     public void init(Context context, TelephonyManager telephonyManager,
235             NetworkAvailableCallBack networkAvailableCallBack) {
236         log("init called");
237         mThread = new HandlerThread(LOG_TAG);
238         mThread.start();
239         mHandler =  new Handler(mThread.getLooper()) {
240             @Override
241             public void handleMessage(Message msg) {
242                 switch (msg.what) {
243                     case MSG_SCAN_RESULTS_AVAILABLE:
244                         logDebug("Msg received for scan results");
245                         /* Todo: need to aggregate the results */
246                         analyzeScanResults((List<CellInfo>) msg.obj);
247                         break;
248                     case MSG_SCAN_COMPLETE:
249                         logDebug("Msg received for scan complete");
250                         restartScan();
251                         break;
252                     case MSG_SCAN_ERROR:
253                         logDebug("Msg received for scan error");
254                         invalidateScanOnError((int) msg.obj);
255                         break;
256                     default:
257                         log("invalid message");
258                         break;
259                 }
260             }
261         };
262         mTelephonyManager = telephonyManager;
263         mNetworkAvailableCallBack = networkAvailableCallBack;
264         configManager = (CarrierConfigManager) context.getSystemService(
265                 Context.CARRIER_CONFIG_SERVICE);
266     }
267 
268     /* get mcc mnc from cell info if the cell is for LTE */
getMccMnc(CellInfo cellInfo)269     private String getMccMnc(CellInfo cellInfo) {
270         if (cellInfo instanceof CellInfoLte) {
271             return ((CellInfoLte) cellInfo).getCellIdentity().getMccString()
272                     + ((CellInfoLte) cellInfo).getCellIdentity().getMncString();
273         }
274 
275         return null;
276     }
277 
getIs4gScanEnabled()278     private boolean getIs4gScanEnabled() {
279         // TODO: make this a null check
280         if (mIs4gScanEnabled != null) {
281             return mIs4gScanEnabled;
282         }
283         return getBooleanCarrierConfig(
284                 CarrierConfigManager.KEY_ENABLE_4G_OPPORTUNISTIC_NETWORK_SCAN_BOOL);
285     }
286 
287     @VisibleForTesting
setIs4gScanEnabled(boolean enabled)288     void setIs4gScanEnabled(boolean enabled) {
289         mIs4gScanEnabled = enabled;
290     }
291 
292     @VisibleForTesting
createNetworkScanRequest(ArrayList<AvailableNetworkInfo> availableNetworks, int periodicity)293     NetworkScanRequest createNetworkScanRequest(ArrayList<AvailableNetworkInfo> availableNetworks,
294         int periodicity) {
295         RadioAccessSpecifier[] ras;
296         ArrayList<String> mccMncs = new ArrayList<String>();
297         Set<Integer> bandSet5G = new ArraySet<>();
298         Set<Integer> bandSet4G = new ArraySet<>();
299 
300         mIs4gScanEnabled = getIs4gScanEnabled();
301 
302         /* retrieve mcc mncs and bands for available networks */
303         for (AvailableNetworkInfo availableNetwork : availableNetworks) {
304             mccMncs.addAll(availableNetwork.getMccMncs());
305             List<RadioAccessSpecifier> radioAccessSpecifiers =
306                     availableNetwork.getRadioAccessSpecifiers();
307             if (radioAccessSpecifiers.isEmpty()) {
308                 if (mIs4gScanEnabled) {
309                     bandSet4G.addAll(availableNetwork.getBands());
310                 }
311                 bandSet5G.addAll(availableNetwork.getBands());
312             } else {
313                 for (RadioAccessSpecifier radioAccessSpecifier : radioAccessSpecifiers) {
314                     int radioAccessNetworkType = radioAccessSpecifier.getRadioAccessNetwork();
315                     if (mIs4gScanEnabled &&
316                             radioAccessNetworkType ==
317                                     AccessNetworkConstants.AccessNetworkType.EUTRAN) {
318                         bandSet4G.addAll(Arrays.stream(radioAccessSpecifier.getBands())
319                                 .boxed().collect(Collectors.toList()));
320                     } else if (radioAccessNetworkType ==
321                             AccessNetworkConstants.AccessNetworkType.NGRAN) {
322                         bandSet5G.addAll(Arrays.stream(radioAccessSpecifier.getBands())
323                                 .boxed().collect(Collectors.toList()));
324                     }
325                 }
326             }
327         }
328 
329         int rasSize = 1;
330         if (mIs4gScanEnabled && bandSet4G.isEmpty() == bandSet5G.isEmpty()) {
331             rasSize = 2;
332         }
333         ras = new RadioAccessSpecifier[rasSize];
334 
335         if (bandSet4G.isEmpty() && bandSet5G.isEmpty()) {
336             // Set the default RadioAccessSpecifiers if none were set and no bands were set.
337             ras[0] = DEFAULT_5G_RAS;
338             if (mIs4gScanEnabled) {
339                 ras[1] = DEFAULT_4G_RAS;
340             }
341         } else {
342             if (mIs4gScanEnabled && !bandSet4G.isEmpty()) {
343                 ras[0] = new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.EUTRAN,
344                         bandSet4G.stream().mapToInt(band->band).toArray(), null);
345             }
346             if (!bandSet5G.isEmpty()) {
347                 ras[rasSize - 1] = new RadioAccessSpecifier(
348                         AccessNetworkConstants.AccessNetworkType.NGRAN,
349                         bandSet5G.stream().mapToInt(band->band).toArray(), null);
350             } else if (!mIs4gScanEnabled) {
351                 // Reached if only 4G was specified but 4G scan is disabled.
352                 ras[0] = DEFAULT_5G_RAS;
353             }
354         }
355 
356         NetworkScanRequest networkScanRequest = new NetworkScanRequest(
357             NetworkScanRequest.SCAN_TYPE_PERIODIC, ras, periodicity, MAX_SEARCH_TIME, false,
358             NetworkScanRequest.MAX_INCREMENTAL_PERIODICITY_SEC, mccMncs);
359         synchronized (mLock) {
360             mMccMncs = mccMncs;
361         }
362         return networkScanRequest;
363     }
364 
365     /**
366      * start less interval network scan
367      * @param availableNetworks list of subscriptions for which the scanning needs to be started.
368      * @return true if successfully accepted request.
369      */
startFastNetworkScan(ArrayList<AvailableNetworkInfo> availableNetworks)370     public boolean startFastNetworkScan(ArrayList<AvailableNetworkInfo> availableNetworks) {
371         NetworkScanRequest networkScanRequest = createNetworkScanRequest(availableNetworks,
372                 SEARCH_PERIODICITY_FAST);
373         return startNetworkScan(networkScanRequest);
374     }
375 
376 
startNetworkScan(NetworkScanRequest networkScanRequest)377     private boolean startNetworkScan(NetworkScanRequest networkScanRequest) {
378         NetworkScan networkScan;
379         synchronized (mLock) {
380             /* if the request is same as existing one, then make sure to not proceed */
381             if (mIsScanActive && mCurrentScanRequest.equals(networkScanRequest)) {
382                 return true;
383             }
384 
385             /* Need to stop current scan if we already have one */
386             stopNetworkScan();
387 
388             /* user lower threshold to enable modem stack */
389             mRsrpEntryThreshold =
390                 getIntCarrierConfig(
391                     CarrierConfigManager.KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSRP_INT);
392 
393             mSsRsrpEntryThreshold = getIntCarrierConfig(
394                     CarrierConfigManager.KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_SS_RSRP_INT);
395 
396             /* start new scan */
397             networkScan = mTelephonyManager.requestNetworkScan(networkScanRequest,
398                     mNetworkScanCallback);
399 
400             mCurrentScan = networkScan;
401             mIsScanActive = true;
402             mCurrentScanRequest = networkScanRequest;
403         }
404 
405         logDebug("startNetworkScan " + networkScanRequest);
406         return true;
407     }
408 
restartScan()409     private void restartScan() {
410         NetworkScan networkScan;
411         logDebug("restartScan");
412         synchronized (mLock) {
413             if (mCurrentScanRequest != null) {
414                 networkScan = mTelephonyManager.requestNetworkScan(mCurrentScanRequest,
415                         mNetworkScanCallback);
416                 mIsScanActive = true;
417             }
418         }
419     }
420 
421     /**
422      * stop network scan
423      */
stopNetworkScan()424     public void stopNetworkScan() {
425         logDebug("stopNetworkScan");
426         synchronized (mLock) {
427             if (mIsScanActive && mCurrentScan != null) {
428                 try {
429                     mCurrentScan.stopScan();
430                 } catch (IllegalArgumentException iae) {
431                     logDebug("Scan failed with exception " + iae);
432                 }
433                 mIsScanActive = false;
434                 mCurrentScan = null;
435                 mCurrentScanRequest = null;
436             }
437         }
438     }
439 
log(String msg)440     private static void log(String msg) {
441         Rlog.d(LOG_TAG, msg);
442     }
443 
logDebug(String msg)444     private static void logDebug(String msg) {
445         if (DBG) {
446             Rlog.d(LOG_TAG, msg);
447         }
448     }
449 }
450