| /* |
| * Copyright (C) 2008 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.phone; |
| |
| import android.app.Service; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.os.Binder; |
| import android.os.Handler; |
| import android.os.IBinder; |
| import android.os.Message; |
| import android.os.RemoteCallbackList; |
| import android.os.RemoteException; |
| import android.telephony.AccessNetworkConstants; |
| import android.telephony.CellIdentityGsm; |
| import android.telephony.CellInfo; |
| import android.telephony.CellInfoGsm; |
| import android.telephony.NetworkScan; |
| import android.telephony.NetworkScanRequest; |
| import android.telephony.RadioAccessSpecifier; |
| import android.telephony.SubscriptionManager; |
| import android.telephony.TelephonyManager; |
| import android.telephony.TelephonyScanManager; |
| import android.util.Log; |
| |
| import com.android.internal.telephony.CellNetworkScanResult; |
| import com.android.internal.telephony.OperatorInfo; |
| import com.android.settingslib.utils.ThreadUtils; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * Service code used to assist in querying the network for service |
| * availability. |
| */ |
| public class NetworkQueryService extends Service { |
| // debug data |
| private static final String LOG_TAG = "NetworkQuery"; |
| private static final boolean DBG = true; |
| |
| // static events |
| private static final int EVENT_NETWORK_SCAN_VIA_PHONE_COMPLETED = 100; |
| private static final int EVENT_NETWORK_SCAN_RESULTS = 200; |
| private static final int EVENT_NETWORK_SCAN_ERROR = 300; |
| private static final int EVENT_NETWORK_SCAN_COMPLETED = 400; |
| |
| // static states indicating the query status of the service |
| private static final int QUERY_READY = -1; |
| private static final int QUERY_IS_RUNNING = -2; |
| |
| // error statuses that will be retured in the callback. |
| public static final int QUERY_OK = 0; |
| public static final int QUERY_EXCEPTION = 1; |
| |
| static final String ACTION_LOCAL_BINDER = "com.android.phone.intent.action.LOCAL_BINDER"; |
| |
| /** state of the query service */ |
| private int mState; |
| |
| private NetworkScan mNetworkScan; |
| |
| // NetworkScanRequest parameters |
| private static final int SCAN_TYPE = NetworkScanRequest.SCAN_TYPE_ONE_SHOT; |
| private static final boolean INCREMENTAL_RESULTS = true; |
| // The parameters below are in seconds |
| private static final int SEARCH_PERIODICITY_SEC = 5; |
| private static final int MAX_SEARCH_TIME_SEC = 300; |
| private static final int INCREMENTAL_RESULTS_PERIODICITY_SEC = 3; |
| |
| /** |
| * Class for clients to access. Because we know this service always |
| * runs in the same process as its clients, we don't need to deal with |
| * IPC. |
| */ |
| public class LocalBinder extends Binder { |
| INetworkQueryService getService() { |
| return mBinder; |
| } |
| } |
| private final IBinder mLocalBinder = new LocalBinder(); |
| |
| /** |
| * Local handler to receive the network query compete callback |
| * from the RIL. |
| */ |
| Handler mHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| // if the scan is complete, broadcast the results. |
| // to all registerd callbacks. |
| case EVENT_NETWORK_SCAN_VIA_PHONE_COMPLETED: |
| if (DBG) log("scan via Phone completed, broadcasting results"); |
| broadcastQueryResults(msg); |
| break; |
| |
| case EVENT_NETWORK_SCAN_RESULTS: |
| if (DBG) log("get scan results, broadcasting results"); |
| broadcastQueryResults(msg); |
| break; |
| |
| case EVENT_NETWORK_SCAN_ERROR: |
| if (DBG) log("get scan error, broadcasting error code"); |
| broadcastQueryResults(msg); |
| break; |
| |
| case EVENT_NETWORK_SCAN_COMPLETED: |
| if (DBG) log("network scan or stop network query completed"); |
| broadcastQueryResults(msg); |
| break; |
| } |
| } |
| }; |
| |
| /** |
| * List of callback objects, also used to synchronize access to |
| * itself and to changes in state. |
| */ |
| final RemoteCallbackList<INetworkQueryServiceCallback> mCallbacks = |
| new RemoteCallbackList<INetworkQueryServiceCallback>(); |
| |
| /** |
| * This implementation of NetworkScanCallbackImpl is used to receive callback notifications from |
| * the Telephony Manager. |
| */ |
| public class NetworkScanCallbackImpl extends TelephonyScanManager.NetworkScanCallback { |
| |
| /** Returns the scan results to the user, this callback will be called at least one time. */ |
| public void onResults(List<CellInfo> results) { |
| if (DBG) log("got network scan results: " + results.size()); |
| Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_RESULTS, results); |
| msg.sendToTarget(); |
| } |
| |
| /** |
| * Informs the user that the scan has stopped. |
| * |
| * This callback will be called when the scan is finished or cancelled by the user. |
| * The related NetworkScanRequest will be deleted after this callback. |
| */ |
| public void onComplete() { |
| if (DBG) log("network scan completed"); |
| Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED); |
| msg.sendToTarget(); |
| } |
| |
| /** |
| * Informs the user that there is some error about the scan. |
| * |
| * This callback will be called whenever there is any error about the scan, and the scan |
| * will be terminated. onComplete() will NOT be called. |
| */ |
| public void onError(int error) { |
| if (DBG) log("network scan got error: " + error); |
| Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_ERROR, error, 0 /* arg2 */); |
| msg.sendToTarget(); |
| } |
| } |
| |
| /** |
| * Implementation of the INetworkQueryService interface. |
| */ |
| private final INetworkQueryService.Stub mBinder = new INetworkQueryService.Stub() { |
| |
| /** |
| * Starts a query with a INetworkQueryServiceCallback object if |
| * one has not been started yet. Ignore the new query request |
| * if the query has been started already. Either way, place the |
| * callback object in the queue to be notified upon request |
| * completion. |
| */ |
| public void startNetworkQuery( |
| INetworkQueryServiceCallback cb, int subId, boolean isIncrementalResult) { |
| if (cb != null) { |
| // register the callback to the list of callbacks. |
| synchronized (mCallbacks) { |
| mCallbacks.register(cb); |
| if (DBG) log("registering callback " + cb.getClass().toString()); |
| |
| switch (mState) { |
| case QUERY_READY: |
| |
| if (isIncrementalResult) { |
| if (DBG) log("start network scan via TelephonManager"); |
| TelephonyManager tm = (TelephonyManager) getSystemService( |
| Context.TELEPHONY_SERVICE); |
| // The Radio Access Specifiers below are meant to scan |
| // all the bands for all the supported technologies. |
| RadioAccessSpecifier gsm = new RadioAccessSpecifier( |
| AccessNetworkConstants.AccessNetworkType.GERAN, |
| null /* bands */, |
| null /* channels */); |
| RadioAccessSpecifier lte = new RadioAccessSpecifier( |
| AccessNetworkConstants.AccessNetworkType.EUTRAN, |
| null /* bands */, |
| null /* channels */); |
| RadioAccessSpecifier wcdma = new RadioAccessSpecifier( |
| AccessNetworkConstants.AccessNetworkType.UTRAN, |
| null /* bands */, |
| null /* channels */); |
| RadioAccessSpecifier[] radioAccessSpecifier = {gsm, lte, wcdma}; |
| NetworkScanRequest networkScanRequest = new NetworkScanRequest( |
| SCAN_TYPE, |
| radioAccessSpecifier, |
| SEARCH_PERIODICITY_SEC, |
| MAX_SEARCH_TIME_SEC, |
| INCREMENTAL_RESULTS, |
| INCREMENTAL_RESULTS_PERIODICITY_SEC, |
| null /* List of PLMN ids (MCC-MNC) */); |
| |
| // Construct a NetworkScanCallback |
| NetworkQueryService.NetworkScanCallbackImpl |
| networkScanCallback = |
| new NetworkQueryService.NetworkScanCallbackImpl(); |
| |
| // Request network scan |
| mNetworkScan = tm.requestNetworkScan(networkScanRequest, |
| networkScanCallback); |
| mState = QUERY_IS_RUNNING; |
| } else { |
| if (SubscriptionManager.isValidSubscriptionId(subId)) { |
| mState = QUERY_IS_RUNNING; |
| ThreadUtils.postOnBackgroundThread(() -> { |
| if (DBG) log("start network scan via Phone xxx"); |
| TelephonyManager telephonyManager = |
| TelephonyManager.from(getApplicationContext()) |
| .createForSubscriptionId(subId); |
| CellNetworkScanResult result = |
| telephonyManager.getAvailableNetworks(); |
| Message msg = mHandler.obtainMessage( |
| EVENT_NETWORK_SCAN_VIA_PHONE_COMPLETED); |
| msg.obj = result; |
| msg.sendToTarget(); |
| }); |
| } else { |
| if (DBG) { |
| log("SubscriptionId is invalid"); |
| } |
| } |
| } |
| |
| break; |
| // do nothing if we're currently busy. |
| case QUERY_IS_RUNNING: |
| if (DBG) log("query already in progress"); |
| break; |
| default: |
| } |
| } |
| } |
| } |
| |
| /** |
| * Stops a query with a INetworkQueryServiceCallback object as |
| * a token. |
| */ |
| public void stopNetworkQuery() { |
| if (DBG) log("stop network query"); |
| // Tells the RIL to terminate the query request. |
| if (mNetworkScan != null) { |
| try { |
| mNetworkScan.stop(); |
| mState = QUERY_READY; |
| } catch (RemoteException e) { |
| if (DBG) log("stop mNetworkScan failed"); |
| } catch (IllegalArgumentException e) { |
| // Do nothing, scan has already completed. |
| } |
| } |
| } |
| |
| /** |
| * Unregisters the callback without impacting an underlying query. |
| */ |
| public void unregisterCallback(INetworkQueryServiceCallback cb) { |
| if (cb != null) { |
| synchronized (mCallbacks) { |
| if (DBG) log("unregistering callback " + cb.getClass().toString()); |
| mCallbacks.unregister(cb); |
| } |
| } |
| } |
| }; |
| |
| @Override |
| public void onCreate() { |
| mState = QUERY_READY; |
| } |
| |
| /** |
| * Required for service implementation. |
| */ |
| @Override |
| public void onStart(Intent intent, int startId) { |
| } |
| |
| /** |
| * Handle the bind request. |
| */ |
| @Override |
| public IBinder onBind(Intent intent) { |
| if (DBG) log("binding service implementation"); |
| if (ACTION_LOCAL_BINDER.equals(intent.getAction())) { |
| return mLocalBinder; |
| } |
| |
| return mBinder; |
| } |
| |
| /** |
| * Broadcast the results from the query to all registered callback objects. |
| */ |
| private void broadcastQueryResults(Message msg) { |
| // reset the state. |
| synchronized (mCallbacks) { |
| mState = QUERY_READY; |
| |
| // Make the calls to all the registered callbacks. |
| for (int i = (mCallbacks.beginBroadcast() - 1); i >= 0; i--) { |
| INetworkQueryServiceCallback cb = mCallbacks.getBroadcastItem(i); |
| if (DBG) log("broadcasting results to " + cb.getClass().toString()); |
| try { |
| switch (msg.what) { |
| case EVENT_NETWORK_SCAN_VIA_PHONE_COMPLETED: |
| CellNetworkScanResult result = (CellNetworkScanResult) msg.obj; |
| if (result.getOperators() != null) { |
| cb.onResults(getCellInfoList(result.getOperators())); |
| } else { |
| if (DBG) log("Operators list is null."); |
| } |
| // Send the onComplete() callback to indicate the one-time network |
| // scan has completed. |
| cb.onComplete(); |
| break; |
| |
| case EVENT_NETWORK_SCAN_RESULTS: |
| cb.onResults((List<CellInfo>) msg.obj); |
| break; |
| |
| case EVENT_NETWORK_SCAN_COMPLETED: |
| cb.onComplete(); |
| break; |
| |
| case EVENT_NETWORK_SCAN_ERROR: |
| cb.onError(msg.arg1); |
| break; |
| } |
| } catch (RemoteException e) { |
| } |
| } |
| |
| // finish up. |
| mCallbacks.finishBroadcast(); |
| } |
| } |
| |
| /** |
| * Wraps up a list of OperatorInfo object to a list of CellInfo object. GsmCellInfo is used here |
| * only because operatorInfo does not contain technology type while CellInfo is an abstract |
| * object that requires to specify technology type. It doesn't matter which CellInfo type to |
| * use here, since we only want to wrap the operator info and PLMN to a CellInfo object. |
| */ |
| private List<CellInfo> getCellInfoList(List<OperatorInfo> operatorInfoList) { |
| List<CellInfo> cellInfoList = new ArrayList<>(); |
| for (OperatorInfo oi: operatorInfoList) { |
| String operatorNumeric = oi.getOperatorNumeric(); |
| String mcc = null; |
| String mnc = null; |
| log("operatorNumeric: " + operatorNumeric); |
| if (operatorNumeric != null && operatorNumeric.matches("^[0-9]{5,6}$")) { |
| mcc = operatorNumeric.substring(0, 3); |
| mnc = operatorNumeric.substring(3); |
| } |
| CellIdentityGsm cig = new CellIdentityGsm( |
| Integer.MAX_VALUE /* lac */, |
| Integer.MAX_VALUE /* cid */, |
| Integer.MAX_VALUE /* arfcn */, |
| Integer.MAX_VALUE /* bsic */, |
| mcc, |
| mnc, |
| oi.getOperatorAlphaLong(), |
| oi.getOperatorAlphaShort()); |
| |
| CellInfoGsm ci = new CellInfoGsm(); |
| ci.setCellIdentity(cig); |
| cellInfoList.add(ci); |
| } |
| return cellInfoList; |
| } |
| |
| private static void log(String msg) { |
| Log.d(LOG_TAG, msg); |
| } |
| } |