blob: e67582fa3929f803013cf9f47f6d9ca1381b6e20 [file] [log] [blame]
/*
* 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);
}
}