• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.settings.network.telephony;
18 
19 import android.annotation.IntDef;
20 import android.content.Context;
21 import android.telephony.AccessNetworkConstants.AccessNetworkType;
22 import android.telephony.CellInfo;
23 import android.telephony.NetworkScan;
24 import android.telephony.NetworkScanRequest;
25 import android.telephony.PhoneCapability;
26 import android.telephony.RadioAccessSpecifier;
27 import android.telephony.TelephonyManager;
28 import android.telephony.TelephonyScanManager;
29 import android.util.Log;
30 
31 import androidx.annotation.VisibleForTesting;
32 
33 import com.android.internal.telephony.CellNetworkScanResult;
34 
35 import com.android.settings.R;
36 
37 import com.google.common.util.concurrent.FutureCallback;
38 import com.google.common.util.concurrent.Futures;
39 import com.google.common.util.concurrent.ListenableFuture;
40 import com.google.common.util.concurrent.MoreExecutors;
41 import com.google.common.util.concurrent.SettableFuture;
42 
43 import java.lang.annotation.Retention;
44 import java.lang.annotation.RetentionPolicy;
45 import java.util.ArrayList;
46 import java.util.Arrays;
47 import java.util.List;
48 import java.util.concurrent.CancellationException;
49 import java.util.concurrent.Executor;
50 import java.util.stream.Collectors;
51 
52 /**
53  * A helper class that builds the common interface and performs the network scan for two different
54  * network scan APIs.
55  */
56 public class NetworkScanHelper {
57     public static final String TAG = "NetworkScanHelper";
58 
59     /**
60      * Callbacks interface to inform the network scan results.
61      */
62     public interface NetworkScanCallback {
63         /**
64          * Called when the results is returned from {@link TelephonyManager}. This method will be
65          * called at least one time if there is no error occurred during the network scan.
66          *
67          * <p> This method can be called multiple times in one network scan, until
68          * {@link #onComplete()} or {@link #onError(int)} is called.
69          *
70          * @param results
71          */
onResults(List<CellInfo> results)72         void onResults(List<CellInfo> results);
73 
74         /**
75          * Called when the current network scan process is finished. No more
76          * {@link #onResults(List)} will be called for the current network scan after this method is
77          * called.
78          */
onComplete()79         void onComplete();
80 
81         /**
82          * Called when an error occurred during the network scan process.
83          *
84          * <p> There is no more result returned from {@link TelephonyManager} if an error occurred.
85          *
86          * <p> {@link #onComplete()} will not be called if an error occurred.
87          *
88          * @see {@link NetworkScan.ScanErrorCode}
89          */
onError(int errorCode)90         void onError(int errorCode);
91     }
92 
93     @Retention(RetentionPolicy.SOURCE)
94     @IntDef({NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS, NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS})
95     public @interface NetworkQueryType {}
96 
97     /**
98      * Performs the network scan using {@link TelephonyManager#getAvailableNetworks()}. The network
99      * scan results won't be returned to the caller until the network scan is completed.
100      *
101      * <p> This is typically used when the modem doesn't support the new network scan api
102      * {@link TelephonyManager#requestNetworkScan(
103      * NetworkScanRequest, Executor, TelephonyScanManager.NetworkScanCallback)}.
104      */
105     public static final int NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS = 1;
106 
107     /**
108      * Performs the network scan using {@link TelephonyManager#requestNetworkScan(
109      * NetworkScanRequest, Executor, TelephonyScanManager.NetworkScanCallback)} The network scan
110      * results will be returned to the caller periodically in a small time window until the network
111      * scan is completed. The complete results should be returned in the last called of
112      * {@link NetworkScanCallback#onResults(List)}.
113      *
114      * <p> This is recommended to be used if modem supports the new network scan api
115      * {@link TelephonyManager#requestNetworkScan(
116      * NetworkScanRequest, Executor, TelephonyScanManager.NetworkScanCallback)}
117      */
118     public static final int NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS = 2;
119 
120     /** The constants below are used in the async network scan. */
121     @VisibleForTesting
122     static final boolean INCREMENTAL_RESULTS = true;
123     @VisibleForTesting
124     static final int SEARCH_PERIODICITY_SEC = 5;
125     @VisibleForTesting
126     static final int MAX_SEARCH_TIME_SEC = 300;
127     @VisibleForTesting
128     static final int INCREMENTAL_RESULTS_PERIODICITY_SEC = 3;
129 
130     private final NetworkScanCallback mNetworkScanCallback;
131     private final TelephonyManager mTelephonyManager;
132     private final TelephonyScanManager.NetworkScanCallback mInternalNetworkScanCallback;
133     private final Executor mExecutor;
134 
135     private int mMaxSearchTimeSec = MAX_SEARCH_TIME_SEC;
136     private NetworkScan mNetworkScanRequester;
137 
138     /** Callbacks for sync network scan */
139     private ListenableFuture<List<CellInfo>> mNetworkScanFuture;
140 
NetworkScanHelper(TelephonyManager tm, NetworkScanCallback callback, Executor executor)141     public NetworkScanHelper(TelephonyManager tm, NetworkScanCallback callback, Executor executor) {
142         mTelephonyManager = tm;
143         mNetworkScanCallback = callback;
144         mInternalNetworkScanCallback = new NetworkScanCallbackImpl();
145         mExecutor = executor;
146     }
147 
NetworkScanHelper(Context context, TelephonyManager tm, NetworkScanCallback callback, Executor executor)148     public NetworkScanHelper(Context context, TelephonyManager tm, NetworkScanCallback callback,
149             Executor executor) {
150         this(tm, callback, executor);
151         mMaxSearchTimeSec = context.getResources().getInteger(
152                 R.integer.config_network_scan_helper_max_search_time_sec);
153     }
154 
155     @VisibleForTesting
createNetworkScanForPreferredAccessNetworks()156     NetworkScanRequest createNetworkScanForPreferredAccessNetworks() {
157         long networkTypeBitmap3gpp = mTelephonyManager.getPreferredNetworkTypeBitmask()
158                 & TelephonyManager.NETWORK_STANDARDS_FAMILY_BITMASK_3GPP;
159 
160         List<RadioAccessSpecifier> radioAccessSpecifiers = new ArrayList<>();
161         // If the allowed network types are unknown or if they are of the right class, scan for
162         // them; otherwise, skip them to save scan time and prevent users from being shown networks
163         // that they can't connect to.
164         if (networkTypeBitmap3gpp == 0
165                 || (networkTypeBitmap3gpp & TelephonyManager.NETWORK_CLASS_BITMASK_2G) != 0) {
166             radioAccessSpecifiers.add(
167                     new RadioAccessSpecifier(AccessNetworkType.GERAN, null, null));
168         }
169         if (networkTypeBitmap3gpp == 0
170                 || (networkTypeBitmap3gpp & TelephonyManager.NETWORK_CLASS_BITMASK_3G) != 0) {
171             radioAccessSpecifiers.add(
172                     new RadioAccessSpecifier(AccessNetworkType.UTRAN, null, null));
173         }
174         if (networkTypeBitmap3gpp == 0
175                 || (networkTypeBitmap3gpp & TelephonyManager.NETWORK_CLASS_BITMASK_4G) != 0) {
176             radioAccessSpecifiers.add(
177                     new RadioAccessSpecifier(AccessNetworkType.EUTRAN, null, null));
178         }
179         // If a device supports 5G stand-alone then the code below should be re-enabled; however
180         // a device supporting only non-standalone mode cannot perform PLMN selection and camp on
181         // a 5G network, which means that it shouldn't scan for 5G at the expense of battery as
182         // part of the manual network selection process.
183         //
184         if (networkTypeBitmap3gpp == 0
185                 || (hasNrSaCapability()
186                 && (networkTypeBitmap3gpp & TelephonyManager.NETWORK_CLASS_BITMASK_5G) != 0)) {
187             radioAccessSpecifiers.add(
188                     new RadioAccessSpecifier(AccessNetworkType.NGRAN, null, null));
189             Log.d(TAG, "radioAccessSpecifiers add NGRAN.");
190         }
191 
192         return new NetworkScanRequest(
193                 NetworkScanRequest.SCAN_TYPE_ONE_SHOT,
194                 radioAccessSpecifiers.toArray(
195                         new RadioAccessSpecifier[radioAccessSpecifiers.size()]),
196                 SEARCH_PERIODICITY_SEC,
197                 mMaxSearchTimeSec,
198                 INCREMENTAL_RESULTS,
199                 INCREMENTAL_RESULTS_PERIODICITY_SEC,
200                 null /* List of PLMN ids (MCC-MNC) */);
201     }
202 
203     /**
204      * Performs a network scan for the given type {@code type}.
205      * {@link #NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS} is recommended if modem supports
206      * {@link TelephonyManager#requestNetworkScan(
207      * NetworkScanRequest, Executor, TelephonyScanManager.NetworkScanCallback)}.
208      *
209      * @param type used to tell which network scan API should be used.
210      */
startNetworkScan(@etworkQueryType int type)211     public void startNetworkScan(@NetworkQueryType int type) {
212         if (type == NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS) {
213             mNetworkScanFuture = SettableFuture.create();
214             Futures.addCallback(mNetworkScanFuture, new FutureCallback<List<CellInfo>>() {
215                 @Override
216                 public void onSuccess(List<CellInfo> result) {
217                     onResults(result);
218                     onComplete();
219                 }
220 
221                 @Override
222                 public void onFailure(Throwable t) {
223                     if (t instanceof CancellationException) {
224                         return;
225                     }
226                     int errCode = Integer.parseInt(t.getMessage());
227                     onError(errCode);
228                 }
229             }, MoreExecutors.directExecutor());
230             mExecutor.execute(new NetworkScanSyncTask(
231                     mTelephonyManager, (SettableFuture) mNetworkScanFuture));
232         } else if (type == NETWORK_SCAN_TYPE_INCREMENTAL_RESULTS) {
233             if (mNetworkScanRequester != null) {
234                 return;
235             }
236             mNetworkScanRequester = mTelephonyManager.requestNetworkScan(
237                     createNetworkScanForPreferredAccessNetworks(),
238                     mExecutor,
239                     mInternalNetworkScanCallback);
240             if (mNetworkScanRequester == null) {
241                 onError(NetworkScan.ERROR_RADIO_INTERFACE_ERROR);
242             }
243         }
244     }
245 
246     /**
247      * The network scan of type {@link #NETWORK_SCAN_TYPE_WAIT_FOR_ALL_RESULTS} can't be stopped,
248      * however, the result of the current network scan won't be returned to the callback after
249      * calling this method.
250      */
stopNetworkQuery()251     public void stopNetworkQuery() {
252         if (mNetworkScanRequester != null) {
253             mNetworkScanRequester.stopScan();
254             mNetworkScanRequester = null;
255         }
256 
257         if (mNetworkScanFuture != null) {
258             mNetworkScanFuture.cancel(true /* mayInterruptIfRunning */);
259             mNetworkScanFuture = null;
260         }
261     }
262 
onResults(List<CellInfo> cellInfos)263     private void onResults(List<CellInfo> cellInfos) {
264         mNetworkScanCallback.onResults(cellInfos);
265     }
266 
onComplete()267     private void onComplete() {
268         mNetworkScanCallback.onComplete();
269     }
270 
onError(int errCode)271     private void onError(int errCode) {
272         mNetworkScanCallback.onError(errCode);
273     }
274 
hasNrSaCapability()275     private boolean hasNrSaCapability() {
276         return Arrays.stream(
277                 mTelephonyManager.getPhoneCapability().getDeviceNrCapabilities())
278                 .anyMatch(i -> i == PhoneCapability.DEVICE_NR_CAPABILITY_SA);
279     }
280 
281     /**
282      * Converts the status code of {@link CellNetworkScanResult} to one of the
283      * {@link NetworkScan.ScanErrorCode}.
284      * @param errCode status code from {@link CellNetworkScanResult}.
285      *
286      * @return one of the scan error code from {@link NetworkScan.ScanErrorCode}.
287      */
convertToScanErrorCode(int errCode)288     private static int convertToScanErrorCode(int errCode) {
289         switch (errCode) {
290             case CellNetworkScanResult.STATUS_RADIO_NOT_AVAILABLE:
291                 return NetworkScan.ERROR_RADIO_INTERFACE_ERROR;
292             case CellNetworkScanResult.STATUS_RADIO_GENERIC_FAILURE:
293             default:
294                 return NetworkScan.ERROR_MODEM_ERROR;
295         }
296     }
297 
298     private final class NetworkScanCallbackImpl extends TelephonyScanManager.NetworkScanCallback {
onResults(List<CellInfo> results)299         public void onResults(List<CellInfo> results) {
300             Log.d(TAG, "Async scan onResults() results = "
301                     + CellInfoUtil.cellInfoListToString(results));
302             NetworkScanHelper.this.onResults(results);
303         }
304 
onComplete()305         public void onComplete() {
306             Log.d(TAG, "async scan onComplete()");
307             NetworkScanHelper.this.onComplete();
308         }
309 
onError(@etworkScan.ScanErrorCode int errCode)310         public void onError(@NetworkScan.ScanErrorCode int errCode) {
311             Log.d(TAG, "async scan onError() errorCode = " + errCode);
312             NetworkScanHelper.this.onError(errCode);
313         }
314     }
315 
316     private static final class NetworkScanSyncTask implements Runnable {
317         private final SettableFuture<List<CellInfo>> mCallback;
318         private final TelephonyManager mTelephonyManager;
319 
NetworkScanSyncTask( TelephonyManager telephonyManager, SettableFuture<List<CellInfo>> callback)320         NetworkScanSyncTask(
321                 TelephonyManager telephonyManager, SettableFuture<List<CellInfo>> callback) {
322             mTelephonyManager = telephonyManager;
323             mCallback = callback;
324         }
325 
326         @Override
run()327         public void run() {
328             final CellNetworkScanResult result = mTelephonyManager.getAvailableNetworks();
329             if (result.getStatus() == CellNetworkScanResult.STATUS_SUCCESS) {
330                 final List<CellInfo> cellInfos = result.getOperators()
331                         .stream()
332                         .map(operatorInfo
333                                 -> CellInfoUtil.convertOperatorInfoToCellInfo(operatorInfo))
334                         .collect(Collectors.toList());
335                 Log.d(TAG, "Sync network scan completed, cellInfos = "
336                         + CellInfoUtil.cellInfoListToString(cellInfos));
337                 mCallback.set(cellInfos);
338             } else {
339                 final Throwable error = new Throwable(
340                         Integer.toString(convertToScanErrorCode(result.getStatus())));
341                 mCallback.setException(error);
342                 Log.d(TAG, "Sync network scan error, ex = " + error);
343             }
344         }
345     }
346 }
347