blob: 4c2c8c70487427c3e35835ed0a5c5d1e70f1a21f [file] [log] [blame]
/*
* Copyright (C) 2014 MediaTek Inc.
*
* 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.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.Message;
import android.os.AsyncResult;
import android.os.SystemProperties;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.telephony.SubscriptionManager;
import com.android.internal.telephony.IccCardConstants;
import com.android.internal.telephony.Phone;
import com.android.internal.telephony.PhoneConstants;
import com.android.internal.telephony.PhoneBase;
import com.android.internal.telephony.PhoneProxy;
import com.android.internal.telephony.TelephonyIntents;
import com.android.internal.util.AsyncChannel;
import com.android.internal.telephony.PhoneFactory;
import com.android.internal.telephony.TelephonyProperties;
import com.android.internal.telephony.DefaultPhoneNotifier;
import com.android.internal.telephony.SubscriptionController;
import android.util.Log;
import java.util.HashSet;
import java.util.Iterator;
import android.os.Registrant;
import android.os.RegistrantList;
import android.telephony.Rlog;
public class DctController extends Handler {
private static final String LOG_TAG = "DctController";
private static final boolean DBG = true;
private static final int EVENT_PHONE1_DETACH = 1;
private static final int EVENT_PHONE2_DETACH = 2;
private static final int EVENT_PHONE3_DETACH = 3;
private static final int EVENT_PHONE4_DETACH = 4;
private static final int EVENT_PHONE1_RADIO_OFF = 5;
private static final int EVENT_PHONE2_RADIO_OFF = 6;
private static final int EVENT_PHONE3_RADIO_OFF = 7;
private static final int EVENT_PHONE4_RADIO_OFF = 8;
private static final int PHONE_NONE = -1;
private static DctController sDctController;
private static final int EVENT_ALL_DATA_DISCONNECTED = 1;
private static final int EVENT_SET_DATA_ALLOW_DONE = 2;
private RegistrantList mNotifyDataSwitchInfo = new RegistrantList();
private SubscriptionController mSubController = SubscriptionController.getInstance();
private Phone mActivePhone;
private int mPhoneNum;
private boolean[] mServicePowerOffFlag;
private PhoneProxy[] mPhones;
private DcSwitchState[] mDcSwitchState;
private DcSwitchAsyncChannel[] mDcSwitchAsyncChannel;
private Handler[] mDcSwitchStateHandler;
private HashSet<String> mApnTypes = new HashSet<String>();
private BroadcastReceiver mDataStateReceiver;
private Context mContext;
private int mCurrentDataPhone = PHONE_NONE;
private int mRequestedDataPhone = PHONE_NONE;
private Handler mRspHander = new Handler() {
public void handleMessage(Message msg){
AsyncResult ar;
switch(msg.what) {
case EVENT_PHONE1_DETACH:
case EVENT_PHONE2_DETACH:
case EVENT_PHONE3_DETACH:
case EVENT_PHONE4_DETACH:
logd("EVENT_PHONE" + msg.what +
"_DETACH: mRequestedDataPhone=" + mRequestedDataPhone);
mCurrentDataPhone = PHONE_NONE;
if (mRequestedDataPhone != PHONE_NONE) {
mCurrentDataPhone = mRequestedDataPhone;
mRequestedDataPhone = PHONE_NONE;
Iterator<String> itrType = mApnTypes.iterator();
while (itrType.hasNext()) {
mDcSwitchAsyncChannel[mCurrentDataPhone].connectSync(itrType.next());
}
mApnTypes.clear();
}
break;
case EVENT_PHONE1_RADIO_OFF:
case EVENT_PHONE2_RADIO_OFF:
case EVENT_PHONE3_RADIO_OFF:
case EVENT_PHONE4_RADIO_OFF:
logd("EVENT_PHONE" + (msg.what - EVENT_PHONE1_RADIO_OFF + 1) + "_RADIO_OFF.");
mServicePowerOffFlag[msg.what - EVENT_PHONE1_RADIO_OFF] = true;
break;
default:
break;
}
}
};
private DefaultPhoneNotifier.IDataStateChangedCallback mDataStateChangedCallback =
new DefaultPhoneNotifier.IDataStateChangedCallback() {
public void onDataStateChanged(long subId, String state, String reason,
String apnName, String apnType, boolean unavailable) {
logd("[DataStateChanged]:" + "state=" + state + ",reason=" + reason
+ ",apnName=" + apnName + ",apnType=" + apnType + ",from subId=" + subId);
int phoneId = SubscriptionManager.getPhoneId(subId);
mDcSwitchState[phoneId].notifyDataConnection(phoneId, state, reason,
apnName, apnType, unavailable);
}
};
private class DataStateReceiver extends BroadcastReceiver {
public void onReceive(Context context, Intent intent) {
synchronized(this) {
if (intent.getAction().equals(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED)) {
ServiceState ss = ServiceState.newFromBundle(intent.getExtras());
long subId = intent.getLongExtra(PhoneConstants.SUBSCRIPTION_KEY, PhoneConstants.SUB1);
int phoneId = SubscriptionManager.getPhoneId(subId);
logd("DataStateReceiver: phoneId= " + phoneId);
// for the case of network out of service when bootup (ignore dummy values too)
if (!SubscriptionManager.isValidSubId(subId) || (subId < 0)) {
// FIXME: Maybe add SM.isRealSubId(subId)??
logd("DataStateReceiver: ignore invalid subId=" + subId);
return;
}
if (!SubscriptionManager.isValidPhoneId(phoneId)) {
logd("DataStateReceiver: ignore invalid phoneId=" + phoneId);
return;
}
boolean prevPowerOff = mServicePowerOffFlag[phoneId];
if (ss != null) {
int state = ss.getState();
switch (state) {
case ServiceState.STATE_POWER_OFF:
mServicePowerOffFlag[phoneId] = true;
logd("DataStateReceiver: STATE_POWER_OFF Intent from phoneId="
+ phoneId);
break;
case ServiceState.STATE_IN_SERVICE:
mServicePowerOffFlag[phoneId] = false;
logd("DataStateReceiver: STATE_IN_SERVICE Intent from phoneId="
+ phoneId);
break;
case ServiceState.STATE_OUT_OF_SERVICE:
logd("DataStateReceiver: STATE_OUT_OF_SERVICE Intent from phoneId="
+ phoneId);
if (mServicePowerOffFlag[phoneId]) {
mServicePowerOffFlag[phoneId] = false;
}
break;
case ServiceState.STATE_EMERGENCY_ONLY:
logd("DataStateReceiver: STATE_EMERGENCY_ONLY Intent from phoneId="
+ phoneId);
break;
default:
logd("DataStateReceiver: SERVICE_STATE_CHANGED invalid state");
break;
}
if (prevPowerOff && mServicePowerOffFlag[phoneId] == false &&
mCurrentDataPhone == PHONE_NONE &&
phoneId == getDataConnectionFromSetting()) {
logd("DataStateReceiver: Current Phone is none and default phoneId="
+ phoneId + ", then enableApnType()");
enableApnType(subId, PhoneConstants.APN_TYPE_DEFAULT);
}
}
}
}
}
}
public DefaultPhoneNotifier.IDataStateChangedCallback getDataStateChangedCallback() {
return mDataStateChangedCallback;
}
public static DctController getInstance() {
if (sDctController == null) {
throw new RuntimeException(
"DCTrackerController.getInstance can't be called before makeDCTController()");
}
return sDctController;
}
public static DctController makeDctController(PhoneProxy[] phones) {
if (sDctController == null) {
sDctController = new DctController(phones);
}
return sDctController;
}
private DctController(PhoneProxy[] phones) {
if (phones == null || phones.length == 0) {
if (phones == null) {
loge("DctController(phones): UNEXPECTED phones=null, ignore");
} else {
loge("DctController(phones): UNEXPECTED phones.length=0, ignore");
}
return;
}
mPhoneNum = phones.length;
mServicePowerOffFlag = new boolean[mPhoneNum];
mPhones = phones;
mDcSwitchState = new DcSwitchState[mPhoneNum];
mDcSwitchAsyncChannel = new DcSwitchAsyncChannel[mPhoneNum];
mDcSwitchStateHandler = new Handler[mPhoneNum];
mActivePhone = mPhones[0];
for (int i = 0; i < mPhoneNum; ++i) {
int phoneId = i;
mServicePowerOffFlag[i] = true;
mDcSwitchState[i] = new DcSwitchState(mPhones[i], "DcSwitchState-" + phoneId, phoneId);
mDcSwitchState[i].start();
mDcSwitchAsyncChannel[i] = new DcSwitchAsyncChannel(mDcSwitchState[i], phoneId);
mDcSwitchStateHandler[i] = new Handler();
int status = mDcSwitchAsyncChannel[i].fullyConnectSync(mPhones[i].getContext(),
mDcSwitchStateHandler[i], mDcSwitchState[i].getHandler());
if (status == AsyncChannel.STATUS_SUCCESSFUL) {
logd("DctController(phones): Connect success: " + i);
} else {
loge("DctController(phones): Could not connect to " + i);
}
mDcSwitchState[i].registerForIdle(mRspHander, EVENT_PHONE1_DETACH + i, null);
// Register for radio state change
PhoneBase phoneBase = (PhoneBase)((PhoneProxy)mPhones[i]).getActivePhone();
phoneBase.mCi.registerForOffOrNotAvailable(mRspHander, EVENT_PHONE1_RADIO_OFF + i, null);
}
mContext = mActivePhone.getContext();
IntentFilter filter = new IntentFilter();
filter.addAction(TelephonyIntents.ACTION_DATA_CONNECTION_FAILED);
filter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
mDataStateReceiver = new DataStateReceiver();
Intent intent = mContext.registerReceiver(mDataStateReceiver, filter);
}
private IccCardConstants.State getIccCardState(int phoneId) {
return mPhones[phoneId].getIccCard().getState();
}
/**
* Enable PDP interface by apn type and phone id
*
* @param type enable pdp interface by apn type, such as PhoneConstants.APN_TYPE_MMS, etc.
* @param subId Indicate which sub to query
* @return PhoneConstants.APN_REQUEST_STARTED: action is already started
* PhoneConstants.APN_ALREADY_ACTIVE: interface has already active
* PhoneConstants.APN_TYPE_NOT_AVAILABLE: invalid APN type
* PhoneConstants.APN_REQUEST_FAILED: request failed
* PhoneConstants.APN_REQUEST_FAILED_DUE_TO_RADIO_OFF: readio turn off
* @see #disableApnType()
*/
public synchronized int enableApnType(long subId, String type) {
int phoneId = SubscriptionManager.getPhoneId(subId);
if (phoneId == PHONE_NONE || !isValidphoneId(phoneId)) {
logw("enableApnType(): with PHONE_NONE or Invalid PHONE ID");
return PhoneConstants.APN_REQUEST_FAILED;
}
logd("enableApnType():type=" + type + ",phoneId=" + phoneId +
",powerOff=" + mServicePowerOffFlag[phoneId]);
if (!PhoneConstants.APN_TYPE_DEFAULT.equals(type)) {
for (int peerphoneId =0; peerphoneId < mPhoneNum; peerphoneId++) {
// check peer Phone has non default APN activated as receiving non default APN request.
if (phoneId == peerphoneId) {
continue;
}
String[] activeApnTypes = mPhones[peerphoneId].getActiveApnTypes();
if (activeApnTypes != null && activeApnTypes.length != 0) {
for (int i=0; i<activeApnTypes.length; i++) {
if (!PhoneConstants.APN_TYPE_DEFAULT.equals(activeApnTypes[i]) &&
mPhones[peerphoneId].getDataConnectionState(activeApnTypes[i]) !=
PhoneConstants.DataState.DISCONNECTED) {
logd("enableApnType:Peer Phone still have non-default active APN type:"+
"activeApnTypes[" + i + "]=" + activeApnTypes[i]);
return PhoneConstants.APN_REQUEST_FAILED;
}
}
}
}
}
logd("enableApnType(): CurrentDataPhone=" +
mCurrentDataPhone + ", RequestedDataPhone=" + mRequestedDataPhone);
if (phoneId == mCurrentDataPhone &&
!mDcSwitchAsyncChannel[mCurrentDataPhone].isIdleOrDeactingSync()) {
mRequestedDataPhone = PHONE_NONE;
logd("enableApnType(): mRequestedDataPhone equals request PHONE ID.");
return mDcSwitchAsyncChannel[phoneId].connectSync(type);
} else {
// Only can switch data when mCurrentDataPhone is PHONE_NONE,
// it is set to PHONE_NONE only as receiving EVENT_PHONEX_DETACH
if (mCurrentDataPhone == PHONE_NONE) {
mCurrentDataPhone = phoneId;
mRequestedDataPhone = PHONE_NONE;
logd("enableApnType(): current PHONE is NONE or IDLE, mCurrentDataPhone=" +
mCurrentDataPhone);
return mDcSwitchAsyncChannel[phoneId].connectSync(type);
} else {
logd("enableApnType(): current PHONE:" + mCurrentDataPhone + " is active.");
if (phoneId != mRequestedDataPhone) {
mApnTypes.clear();
}
mApnTypes.add(type);
mRequestedDataPhone = phoneId;
mDcSwitchState[mCurrentDataPhone].cleanupAllConnection();
}
}
return PhoneConstants.APN_REQUEST_STARTED;
}
/**
* disable PDP interface by apn type and sub id
*
* @param type enable pdp interface by apn type, such as PhoneConstants.APN_TYPE_MMS, etc.
* @param subId Indicate which sub to query
* @return PhoneConstants.APN_REQUEST_STARTED: action is already started
* PhoneConstants.APN_ALREADY_ACTIVE: interface has already active
* PhoneConstants.APN_TYPE_NOT_AVAILABLE: invalid APN type
* PhoneConstants.APN_REQUEST_FAILED: request failed
* PhoneConstants.APN_REQUEST_FAILED_DUE_TO_RADIO_OFF: readio turn off
* @see #enableApnTypeGemini()
*/
public synchronized int disableApnType(long subId, String type) {
int phoneId = SubscriptionManager.getPhoneId(subId);
if (phoneId == PHONE_NONE || !isValidphoneId(phoneId)) {
logw("disableApnType(): with PHONE_NONE or Invalid PHONE ID");
return PhoneConstants.APN_REQUEST_FAILED;
}
logd("disableApnType():type=" + type + ",phoneId=" + phoneId +
",powerOff=" + mServicePowerOffFlag[phoneId]);
return mDcSwitchAsyncChannel[phoneId].disconnectSync(type);
}
public boolean isDataConnectivityPossible(String type, int phoneId) {
if (phoneId == PHONE_NONE || !isValidphoneId(phoneId)) {
logw("isDataConnectivityPossible(): with PHONE_NONE or Invalid PHONE ID");
return false;
} else {
return mPhones[phoneId].isDataConnectivityPossible(type);
}
}
public boolean isIdleOrDeacting(int phoneId) {
if (mDcSwitchAsyncChannel[phoneId].isIdleOrDeactingSync()) {
return true;
} else {
return false;
}
}
private boolean isValidphoneId(int phoneId) {
return phoneId >= 0 && phoneId <= mPhoneNum;
}
private boolean isValidApnType(String apnType) {
if (apnType.equals(PhoneConstants.APN_TYPE_DEFAULT)
|| apnType.equals(PhoneConstants.APN_TYPE_MMS)
|| apnType.equals(PhoneConstants.APN_TYPE_SUPL)
|| apnType.equals(PhoneConstants.APN_TYPE_DUN)
|| apnType.equals(PhoneConstants.APN_TYPE_HIPRI)
|| apnType.equals(PhoneConstants.APN_TYPE_FOTA)
|| apnType.equals(PhoneConstants.APN_TYPE_IMS)
|| apnType.equals(PhoneConstants.APN_TYPE_CBS))
{
return true;
} else {
return false;
}
}
private int getDataConnectionFromSetting(){
long [] subId = SubscriptionManager.getSubId(PhoneConstants.SIM_ID_1);
int phoneId = SubscriptionManager.getPhoneId(subId[0]);
return phoneId;
}
private static void logv(String s) {
Log.v(LOG_TAG, "[DctController] " + s);
}
private static void logd(String s) {
Log.d(LOG_TAG, "[DctController] " + s);
}
private static void logw(String s) {
Log.w(LOG_TAG, "[DctController] " + s);
}
private static void loge(String s) {
Log.e(LOG_TAG, "[DctController] " + s);
}
public void setDataSubId(long subId) {
//FIXME This should rework
//FIXME Need to have a StateMachine logic to handle this api considering various clients
Rlog.d(LOG_TAG, "setDataAllowed subId :" + subId);
int phoneId = mSubController.getPhoneId(subId);
int prefPhoneId = mSubController.getPhoneId(mSubController.getDefaultDataSubId());
Phone phone = mPhones[prefPhoneId].getActivePhone();
DcTrackerBase dcTracker =((PhoneBase)phone).mDcTracker;
dcTracker.setDataAllowed(false, null);
mPhones[prefPhoneId].registerForAllDataDisconnected(
this, EVENT_ALL_DATA_DISCONNECTED, new Integer(phoneId));
}
public void registerForDataSwitchInfo(Handler h, int what, Object obj) {
//FIXME This should rework
Registrant r = new Registrant (h, what, obj);
synchronized (mNotifyDataSwitchInfo) {
mNotifyDataSwitchInfo.add(r);
}
}
@Override
public void handleMessage (Message msg) {
//FIXME This should rework
AsyncResult ar = (AsyncResult)msg.obj;
Rlog.d(LOG_TAG, "handleMessage msg=" + msg);
switch (msg.what) {
case EVENT_ALL_DATA_DISCONNECTED:
Integer phoneId = (Integer)ar.userObj;
int prefPhoneId = mSubController.getPhoneId(
mSubController.getDefaultDataSubId());
Rlog.d(LOG_TAG, "EVENT_ALL_DATA_DISCONNECTED phoneId :" + phoneId);
mPhones[prefPhoneId].unregisterForAllDataDisconnected(this);
Message alllowedDataDone = Message.obtain(this, EVENT_SET_DATA_ALLOW_DONE,
new Integer(phoneId));
Phone phone = mPhones[phoneId].getActivePhone();
DcTrackerBase dcTracker =((PhoneBase)phone).mDcTracker;
dcTracker.setDataAllowed(true, alllowedDataDone);
break;
case EVENT_SET_DATA_ALLOW_DONE:
phoneId = (Integer)ar.userObj;
long[] subId = mSubController.getSubId(phoneId);
Rlog.d(LOG_TAG, "EVENT_SET_DATA_ALLOWED_DONE phoneId :" + subId[0]);
mNotifyDataSwitchInfo.notifyRegistrants(new AsyncResult(null, subId[0], null));
mPhones[phoneId].updateDataConnectionTracker();
break;
}
}
}