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