blob: 555b6e92c421fc79eeb0eb4d43e02f136d5b7e9d [file] [log] [blame]
/*
* Copyright 2018 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.internal.telephony.dataconnection;
import android.hardware.radio.V1_0.SetupDataCallResult;
import android.net.LinkAddress;
import android.net.LinkProperties;
import android.net.NetworkUtils;
import android.os.AsyncResult;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;
import android.telephony.Rlog;
import android.telephony.SubscriptionManager;
import android.telephony.data.DataCallResponse;
import android.telephony.data.DataProfile;
import android.telephony.data.DataService;
import android.telephony.data.DataServiceCallback;
import android.text.TextUtils;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneFactory;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* This class represents cellular data service which handles telephony data requests and response
* from the cellular modem.
*/
public class CellularDataService extends DataService {
private static final String TAG = CellularDataService.class.getSimpleName();
private static final boolean DBG = false;
private static final int SETUP_DATA_CALL_COMPLETE = 1;
private static final int DEACTIVATE_DATA_ALL_COMPLETE = 2;
private static final int SET_INITIAL_ATTACH_APN_COMPLETE = 3;
private static final int SET_DATA_PROFILE_COMPLETE = 4;
private static final int GET_DATA_CALL_LIST_COMPLETE = 5;
private static final int DATA_CALL_LIST_CHANGED = 6;
private class CellularDataServiceProvider extends DataService.DataServiceProvider {
private final Map<Message, DataServiceCallback> mCallbackMap = new HashMap<>();
private final Looper mLooper;
private final Handler mHandler;
private final HandlerThread mHandlerThread;
private final Phone mPhone;
private CellularDataServiceProvider(int slotId) {
super(slotId);
mPhone = PhoneFactory.getPhone(getSlotId());
mHandlerThread = new HandlerThread(CellularDataService.class.getSimpleName());
mHandlerThread.start();
mLooper = mHandlerThread.getLooper();
mHandler = new Handler(mLooper) {
@Override
public void handleMessage(Message message) {
DataServiceCallback callback = mCallbackMap.remove(message);
AsyncResult ar = (AsyncResult) message.obj;
switch (message.what) {
case SETUP_DATA_CALL_COMPLETE:
SetupDataCallResult result = (SetupDataCallResult) ar.result;
callback.onSetupDataCallComplete(ar.exception != null
? DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE
: DataServiceCallback.RESULT_SUCCESS,
convertDataCallResult(result));
break;
case DEACTIVATE_DATA_ALL_COMPLETE:
callback.onDeactivateDataCallComplete(ar.exception != null
? DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE
: DataServiceCallback.RESULT_SUCCESS);
break;
case SET_INITIAL_ATTACH_APN_COMPLETE:
callback.onSetInitialAttachApnComplete(ar.exception != null
? DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE
: DataServiceCallback.RESULT_SUCCESS);
break;
case SET_DATA_PROFILE_COMPLETE:
callback.onSetDataProfileComplete(ar.exception != null
? DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE
: DataServiceCallback.RESULT_SUCCESS);
break;
case GET_DATA_CALL_LIST_COMPLETE:
callback.onGetDataCallListComplete(
ar.exception != null
? DataServiceCallback.RESULT_ERROR_ILLEGAL_STATE
: DataServiceCallback.RESULT_SUCCESS,
ar.exception != null
? null
: getDataCallList((List<SetupDataCallResult>) ar.result)
);
break;
case DATA_CALL_LIST_CHANGED:
notifyDataCallListChanged(getDataCallList(
(List<SetupDataCallResult>) ar.result));
break;
default:
loge("Unexpected event: " + message.what);
return;
}
}
};
if (DBG) log("Register for data call list changed.");
mPhone.mCi.registerForDataCallListChanged(mHandler, DATA_CALL_LIST_CHANGED, null);
}
private List<DataCallResponse> getDataCallList(List<SetupDataCallResult> dcList) {
List<DataCallResponse> dcResponseList = new ArrayList<>();
for (SetupDataCallResult dcResult : dcList) {
dcResponseList.add(convertDataCallResult(dcResult));
}
return dcResponseList;
}
@Override
public void setupDataCall(int radioTechnology, DataProfile dataProfile, boolean isRoaming,
boolean allowRoaming, int reason, LinkProperties linkProperties,
DataServiceCallback callback) {
if (DBG) log("setupDataCall " + getSlotId());
Message message = null;
// Only obtain the message when the caller wants a callback. If the caller doesn't care
// the request completed or results, then no need to pass the message down.
if (callback != null) {
message = Message.obtain(mHandler, SETUP_DATA_CALL_COMPLETE);
mCallbackMap.put(message, callback);
}
mPhone.mCi.setupDataCall(radioTechnology, dataProfile, isRoaming, allowRoaming, reason,
linkProperties, message);
}
@Override
public void deactivateDataCall(int cid, int reason, DataServiceCallback callback) {
if (DBG) log("deactivateDataCall " + getSlotId());
Message message = null;
// Only obtain the message when the caller wants a callback. If the caller doesn't care
// the request completed or results, then no need to pass the message down.
if (callback != null) {
message = Message.obtain(mHandler, DEACTIVATE_DATA_ALL_COMPLETE);
mCallbackMap.put(message, callback);
}
mPhone.mCi.deactivateDataCall(cid, reason, message);
}
@Override
public void setInitialAttachApn(DataProfile dataProfile, boolean isRoaming,
DataServiceCallback callback) {
if (DBG) log("setInitialAttachApn " + getSlotId());
Message message = null;
// Only obtain the message when the caller wants a callback. If the caller doesn't care
// the request completed or results, then no need to pass the message down.
if (callback != null) {
message = Message.obtain(mHandler, SET_INITIAL_ATTACH_APN_COMPLETE);
mCallbackMap.put(message, callback);
}
mPhone.mCi.setInitialAttachApn(dataProfile, isRoaming, message);
}
@Override
public void setDataProfile(List<DataProfile> dps, boolean isRoaming,
DataServiceCallback callback) {
if (DBG) log("setDataProfile " + getSlotId());
Message message = null;
// Only obtain the message when the caller wants a callback. If the caller doesn't care
// the request completed or results, then no need to pass the message down.
if (callback != null) {
message = Message.obtain(mHandler, SET_DATA_PROFILE_COMPLETE);
mCallbackMap.put(message, callback);
}
mPhone.mCi.setDataProfile(dps.toArray(new DataProfile[dps.size()]), isRoaming, message);
}
@Override
public void getDataCallList(DataServiceCallback callback) {
if (DBG) log("getDataCallList " + getSlotId());
Message message = null;
// Only obtain the message when the caller wants a callback. If the caller doesn't care
// the request completed or results, then no need to pass the message down.
if (callback != null) {
message = Message.obtain(mHandler, GET_DATA_CALL_LIST_COMPLETE);
mCallbackMap.put(message, callback);
}
mPhone.mCi.getDataCallList(message);
}
@Override
public void close() {
mPhone.mCi.unregisterForDataCallListChanged(mHandler);
mHandlerThread.quit();
}
}
@Override
public DataServiceProvider createDataServiceProvider(int slotId) {
log("Cellular data service created for slot " + slotId);
if (!SubscriptionManager.isValidSlotIndex(slotId)) {
loge("Tried to cellular data service with invalid slotId " + slotId);
return null;
}
return new CellularDataServiceProvider(slotId);
}
/**
* Convert SetupDataCallResult defined in types.hal into DataCallResponse
* @param dcResult setup data call result
* @return converted DataCallResponse object
*/
@VisibleForTesting
public DataCallResponse convertDataCallResult(SetupDataCallResult dcResult) {
if (dcResult == null) return null;
// Process address
String[] addresses = null;
if (!TextUtils.isEmpty(dcResult.addresses)) {
addresses = dcResult.addresses.split("\\s+");
}
List<LinkAddress> laList = new ArrayList<>();
if (addresses != null) {
for (String address : addresses) {
address = address.trim();
if (address.isEmpty()) continue;
try {
LinkAddress la;
// Check if the address contains prefix length. If yes, LinkAddress
// can parse that.
if (address.split("/").length == 2) {
la = new LinkAddress(address);
} else {
InetAddress ia = NetworkUtils.numericToInetAddress(address);
la = new LinkAddress(ia, (ia instanceof Inet4Address) ? 32 : 128);
}
laList.add(la);
} catch (IllegalArgumentException e) {
loge("Unknown address: " + address + ", exception = " + e);
}
}
}
// Process dns
String[] dnses = null;
if (!TextUtils.isEmpty(dcResult.dnses)) {
dnses = dcResult.dnses.split("\\s+");
}
List<InetAddress> dnsList = new ArrayList<>();
if (dnses != null) {
for (String dns : dnses) {
dns = dns.trim();
InetAddress ia;
try {
ia = NetworkUtils.numericToInetAddress(dns);
dnsList.add(ia);
} catch (IllegalArgumentException e) {
loge("Unknown dns: " + dns + ", exception = " + e);
}
}
}
// Process gateway
String[] gateways = null;
if (!TextUtils.isEmpty(dcResult.gateways)) {
gateways = dcResult.gateways.split("\\s+");
}
List<InetAddress> gatewayList = new ArrayList<>();
if (gateways != null) {
for (String gateway : gateways) {
gateway = gateway.trim();
InetAddress ia;
try {
ia = NetworkUtils.numericToInetAddress(gateway);
gatewayList.add(ia);
} catch (IllegalArgumentException e) {
loge("Unknown gateway: " + gateway + ", exception = " + e);
}
}
}
return new DataCallResponse(dcResult.status,
dcResult.suggestedRetryTime,
dcResult.cid,
dcResult.active,
dcResult.type,
dcResult.ifname,
laList,
dnsList,
gatewayList,
new ArrayList<>(Arrays.asList(dcResult.pcscf.trim().split("\\s+"))),
dcResult.mtu
);
}
private void log(String s) {
Rlog.d(TAG, s);
}
private void loge(String s) {
Rlog.e(TAG, s);
}
}