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.List;
44 import java.util.Set;
45 import java.util.concurrent.TimeUnit;
46 import java.util.stream.Collectors;
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 */
269     @VisibleForTesting
getMccMnc(CellInfo cellInfo)270     protected String getMccMnc(CellInfo cellInfo) {
271         if (cellInfo instanceof CellInfoLte) {
272             return ((CellInfoLte) cellInfo).getCellIdentity().getMccString()
273                     + ((CellInfoLte) cellInfo).getCellIdentity().getMncString();
274         }
275         else if (cellInfo instanceof CellInfoNr) {
276             return ((CellInfoNr) cellInfo).getCellIdentity().getMccString()
277                     + ((CellInfoNr) cellInfo).getCellIdentity().getMncString();
278         }
279 
280         return null;
281     }
282 
getIs4gScanEnabled()283     private boolean getIs4gScanEnabled() {
284         // TODO: make this a null check
285         if (mIs4gScanEnabled != null) {
286             return mIs4gScanEnabled;
287         }
288         return getBooleanCarrierConfig(
289                 CarrierConfigManager.KEY_ENABLE_4G_OPPORTUNISTIC_NETWORK_SCAN_BOOL);
290     }
291 
292     @VisibleForTesting
setIs4gScanEnabled(boolean enabled)293     void setIs4gScanEnabled(boolean enabled) {
294         mIs4gScanEnabled = enabled;
295     }
296 
297     @VisibleForTesting
createNetworkScanRequest(ArrayList<AvailableNetworkInfo> availableNetworks, int periodicity)298     NetworkScanRequest createNetworkScanRequest(ArrayList<AvailableNetworkInfo> availableNetworks,
299         int periodicity) {
300         RadioAccessSpecifier[] ras;
301         ArrayList<String> mccMncs = new ArrayList<String>();
302         Set<Integer> bandSet5G = new ArraySet<>();
303         Set<Integer> bandSet4G = new ArraySet<>();
304 
305         mIs4gScanEnabled = getIs4gScanEnabled();
306 
307         /* retrieve mcc mncs and bands for available networks */
308         for (AvailableNetworkInfo availableNetwork : availableNetworks) {
309             mccMncs.addAll(availableNetwork.getMccMncs());
310             List<RadioAccessSpecifier> radioAccessSpecifiers =
311                     availableNetwork.getRadioAccessSpecifiers();
312             if (radioAccessSpecifiers.isEmpty()) {
313                 if (mIs4gScanEnabled) {
314                     bandSet4G.addAll(availableNetwork.getBands());
315                 }
316                 bandSet5G.addAll(availableNetwork.getBands());
317             } else {
318                 for (RadioAccessSpecifier radioAccessSpecifier : radioAccessSpecifiers) {
319                     int radioAccessNetworkType = radioAccessSpecifier.getRadioAccessNetwork();
320                     if (mIs4gScanEnabled &&
321                             radioAccessNetworkType ==
322                                     AccessNetworkConstants.AccessNetworkType.EUTRAN) {
323                         bandSet4G.addAll(Arrays.stream(radioAccessSpecifier.getBands())
324                                 .boxed().collect(Collectors.toList()));
325                     } else if (radioAccessNetworkType ==
326                             AccessNetworkConstants.AccessNetworkType.NGRAN) {
327                         bandSet5G.addAll(Arrays.stream(radioAccessSpecifier.getBands())
328                                 .boxed().collect(Collectors.toList()));
329                     }
330                 }
331             }
332         }
333 
334         int rasSize = 1;
335         if (mIs4gScanEnabled && bandSet4G.isEmpty() == bandSet5G.isEmpty()) {
336             rasSize = 2;
337         }
338         ras = new RadioAccessSpecifier[rasSize];
339 
340         if (bandSet4G.isEmpty() && bandSet5G.isEmpty()) {
341             // Set the default RadioAccessSpecifiers if none were set and no bands were set.
342             ras[0] = DEFAULT_5G_RAS;
343             if (mIs4gScanEnabled) {
344                 ras[1] = DEFAULT_4G_RAS;
345             }
346         } else {
347             if (mIs4gScanEnabled && !bandSet4G.isEmpty()) {
348                 ras[0] = new RadioAccessSpecifier(AccessNetworkConstants.AccessNetworkType.EUTRAN,
349                         bandSet4G.stream().mapToInt(band->band).toArray(), null);
350             }
351             if (!bandSet5G.isEmpty()) {
352                 ras[rasSize - 1] = new RadioAccessSpecifier(
353                         AccessNetworkConstants.AccessNetworkType.NGRAN,
354                         bandSet5G.stream().mapToInt(band->band).toArray(), null);
355             } else if (!mIs4gScanEnabled) {
356                 // Reached if only 4G was specified but 4G scan is disabled.
357                 ras[0] = DEFAULT_5G_RAS;
358             }
359         }
360 
361         NetworkScanRequest networkScanRequest = new NetworkScanRequest(
362             NetworkScanRequest.SCAN_TYPE_PERIODIC, ras, periodicity, MAX_SEARCH_TIME, false,
363             NetworkScanRequest.MAX_INCREMENTAL_PERIODICITY_SEC, mccMncs);
364         synchronized (mLock) {
365             mMccMncs = mccMncs;
366         }
367         return networkScanRequest;
368     }
369 
370     /**
371      * start less interval network scan
372      * @param availableNetworks list of subscriptions for which the scanning needs to be started.
373      * @return true if successfully accepted request.
374      */
startFastNetworkScan(ArrayList<AvailableNetworkInfo> availableNetworks)375     public boolean startFastNetworkScan(ArrayList<AvailableNetworkInfo> availableNetworks) {
376         NetworkScanRequest networkScanRequest = createNetworkScanRequest(availableNetworks,
377                 SEARCH_PERIODICITY_FAST);
378         return startNetworkScan(networkScanRequest);
379     }
380 
381 
startNetworkScan(NetworkScanRequest networkScanRequest)382     private boolean startNetworkScan(NetworkScanRequest networkScanRequest) {
383         NetworkScan networkScan;
384         synchronized (mLock) {
385             /* if the request is same as existing one, then make sure to not proceed */
386             if (mIsScanActive && mCurrentScanRequest.equals(networkScanRequest)) {
387                 return true;
388             }
389 
390             /* Need to stop current scan if we already have one */
391             stopNetworkScan();
392 
393             /* user lower threshold to enable modem stack */
394             mRsrpEntryThreshold =
395                 getIntCarrierConfig(
396                     CarrierConfigManager.KEY_OPPORTUNISTIC_NETWORK_EXIT_THRESHOLD_RSRP_INT);
397 
398             mSsRsrpEntryThreshold = getIntCarrierConfig(
399                     CarrierConfigManager.OpportunisticNetwork.KEY_ENTRY_THRESHOLD_SS_RSRP_INT);
400 
401             /* start new scan */
402             networkScan = mTelephonyManager.requestNetworkScan(networkScanRequest,
403                     mNetworkScanCallback);
404 
405             mCurrentScan = networkScan;
406             mIsScanActive = true;
407             mCurrentScanRequest = networkScanRequest;
408         }
409 
410         logDebug("startNetworkScan " + networkScanRequest);
411         return true;
412     }
413 
restartScan()414     private void restartScan() {
415         NetworkScan networkScan;
416         logDebug("restartScan");
417         synchronized (mLock) {
418             if (mCurrentScanRequest != null) {
419                 networkScan = mTelephonyManager.requestNetworkScan(mCurrentScanRequest,
420                         mNetworkScanCallback);
421                 mIsScanActive = true;
422             }
423         }
424     }
425 
426     /**
427      * stop network scan
428      */
stopNetworkScan()429     public void stopNetworkScan() {
430         logDebug("stopNetworkScan");
431         synchronized (mLock) {
432             if (mIsScanActive && mCurrentScan != null) {
433                 try {
434                     mCurrentScan.stopScan();
435                 } catch (IllegalArgumentException iae) {
436                     logDebug("Scan failed with exception " + iae);
437                 }
438                 mIsScanActive = false;
439                 mCurrentScan = null;
440                 mCurrentScanRequest = null;
441             }
442         }
443     }
444 
log(String msg)445     private static void log(String msg) {
446         Rlog.d(LOG_TAG, msg);
447     }
448 
logDebug(String msg)449     private static void logDebug(String msg) {
450         if (DBG) {
451             Rlog.d(LOG_TAG, msg);
452         }
453     }
454 }
455