基于高通Android7.1平台
Call Forwarding业务
CFU Call Forwarding Unconditional
CFB Call Forwarding on Mobile Subscriber Busy
CFNRy Call Forwarding on No Reply
CFNRc Call Forwarding on Mobile Subscriber Not Reachable
呼叫转移(Call Forwarding)业务的作用是将来电转移到其它电话号码上。
SIM卡中也会保存CF,有啥用?SIMRecords.mEfCfis
分类
根据不同的情形,呼叫转移可分为如下四种。
CFU(Call Forwarding Unconditional):无条件转移。所有来电都会被转移到设置的号码,此项设置后,其余几项设置无效。
CFB(Call Forwarding on Mobile Subscriber Busy):用户忙时转移。通话中有新的来电时,新的来电会被转移到设置的号码。拒接会转移?
CFNRy(Call Forwarding on No Reply):无应答时转移。一定时间未接听时转移到设置的号码。
CFNRc(Call Forwarding on Mobile Subscriber Not Reachable):无法接通时转移。不在服务区或关机等情况时转移到设置的号码。
操作
呼叫转移的设置/查询/取消方法。
我们既可以通过在Dialer下拨打相应号码、也可以通过菜单来进行操作。
Dialer操作:
功能 | Activation | Deactivation | Invocation | Registration | Erasure |
---|---|---|---|---|---|
无条件转移 | *21# | #21# | *#21# | **21*number# | ##21# |
用户忙时转移 | *67# | #67# | *#67# | **67*number# | ##67# |
无应答时转移 | *61# | #61# | *#61# | **61*number# | ##61# |
无法接通时转移 | *62# | #62# | *#62# | **62*number# | ##62# |
002:all CF
004:all conditional CF
根据TS 22.030 Annex B,我们可以知道,无应答转移是可以通过SIC设置时间的,
例如,设置15s无应答转移:
**61*123456**15#
当执行Activation时,若后面有转接号码,则会被当作Registration。
例如:
*21*123456#
其作用跟下面的相同:
**21*123456#
##21# Erasure后,再执行*21#,居然提示成功,但实际上CFU还是disable的。
参考:
https://en.wikipedia.org/wiki/Call_forwarding
需要打开数据连接才能操作CF?? no!! 不需要数据链接。之前cmcc的non-dds是禁止CF的,但L+L开始2张卡都支持CF了。
// Call Forwarding unconditional Timer
private static final String SC_CFUT = "22";
private static final String SC_CF_All = "002";
private static final String SC_CF_All_Conditional = "004";
查询/设置
CS/IMS
Dialer/CallSetting
For CS:
For IMS:
GsmCdmaPhone.java
public Connection dial(String dialString, UUSInfo uusInfo, int videoState, Bundle intentExtras)
throws CallStateException {
......
// 判断是否为补充业务
boolean isUt = (dialPart.startsWith("*") || dialPart.startsWith("#"))
&& dialPart.endsWith("#");
// 是否使用IMS配置补充业务
boolean useImsForUt = imsPhone != null && imsPhone.isUtEnabled();
......
if ((imsUseEnabled && (!isUt || useImsForUt)) || useImsForEmergency) {
......
// 通过IMS配置补充业务
return imsPhone.dial(dialString, uusInfo, videoState, intentExtras);
......
}
......
if (isPhoneTypeGsm()) {
// 通过CS配置补充业务
return dialInternal(dialString, null, VideoProfile.STATE_AUDIO_ONLY, intentExtras);
......
}
For IMS:
ImsPhone.java
public Connection dial(String dialString, UUSInfo uusInfo, int videoState, Bundle intentExtras)
throws CallStateException {
return dialInternal (dialString, videoState, intentExtras);
}
private Connection dialInternal(String dialString, int videoState, Bundle intentExtras)
throws CallStateException {
......
// 将用户拨打的号码封装为ImsPhoneMmiCode对象
String networkPortion = PhoneNumberUtils.extractNetworkPortionAlt(newDialString);
ImsPhoneMmiCode mmi =
ImsPhoneMmiCode.newFromDialString(networkPortion, this);
......
if (mmi == null) {
....
} else if (mmi.isTemporaryModeCLIR()) {
....
} else if (!mmi.isSupportedOverImsPhone()) {
....
} else {
mPendingMMIs.add(mmi);
mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
mmi.processCode(); // 处理MMI
return null;
}
}
ImsPhoneMmiCode会针对各种mmi进行处理,我们这里仅讨论CallForwarding。
public void processCode () throws CallStateException {
try {
if (isShortCode()) {
....
} else if (isServiceCodeCallForwarding(mSc)) {
......
if (isInterrogate()) { // 判断是否为查询命令(以"*#"开头)
// 调用ImsPhone的接口查询CallForwarding
mPhone.getCallForwardingOption(reason,
obtainMessage(EVENT_QUERY_CF_COMPLETE, this));
} else {
// 不是查询命令
int cfAction;
// 解析命令类型,需要注意的是,对于Activation(以"*"开头),如果后面跟了转接号码,
// 那么根据3GPP TS 22.030 6.5.2,我们需要将其解析为Registration。
if (isActivate()) {
// 3GPP TS 22.030 6.5.2
// a call forwarding request with a single * would be
// interpreted as registration if containing a forwarded-to
// number, or an activation if not
if (isEmptyOrNull(dialingNumber)) {
cfAction = CommandsInterface.CF_ACTION_ENABLE;
mIsCallFwdReg = false;
} else {
cfAction = CommandsInterface.CF_ACTION_REGISTRATION;
mIsCallFwdReg = true;
}
} else if (isDeactivate()) {
cfAction = CommandsInterface.CF_ACTION_DISABLE;
} else if (isRegister()) {
cfAction = CommandsInterface.CF_ACTION_REGISTRATION;
} else if (isErasure()) {
cfAction = CommandsInterface.CF_ACTION_ERASURE;
} else {
throw new RuntimeException ("invalid action");
}
......
// 调用ImsPhone的接口设置CallForwarding
mPhone.setCallForwardingOption(cfAction, reason,
dialingNumber, serviceClass, time, obtainMessage(
EVENT_SET_CFF_COMPLETE,
isSettingUnconditional,
isEnableDesired, this));
}
} else if (isServiceCodeCallBarring(mSc)) {
......
} else if (mSc != null && mSc.equals(SC_CLIR)) {
......
} else if (mSc != null && mSc.equals(SC_CLIP)) {
......
} else if (mSc != null && mSc.equals(SC_COLP)) {
......
} else if (mSc != null && mSc.equals(SC_COLR)) {
......
} else if (mSc != null && (mSc.equals(SC_BS_MT))) {
......
} else if (mSc != null && mSc.equals(SC_BAICa)) {
......
} else if (mSc != null && mSc.equals(SC_WAIT)) {
......
} else if (mPoundString != null) {
......
} else {
throw new RuntimeException ("Invalid or Unsupported MMI Code");
}
} catch (RuntimeException exc) {
......
}
}
从这里开始,查询和设置的流程就不同了。
先来看查询CallForwarding
查询CallForwarding
ImsPhone.getCallForwardingOption()中先判断CallForwarding的reason是否有效,然后再调用
ImsUt.queryCallForward()进行查询。
public void getCallForwardingOption(int commandInterfaceCFReason,
Message onComplete) {
if (DBG) Rlog.d(LOG_TAG, "getCallForwardingOption reason=" + commandInterfaceCFReason);
if (isValidCommandInterfaceCFReason(commandInterfaceCFReason)) {
if (DBG) Rlog.d(LOG_TAG, "requesting call forwarding query.");
Message resp;
resp = obtainMessage(EVENT_GET_CALL_FORWARD_DONE, onComplete);
try {
ImsUtInterface ut = mCT.getUtInterface();
ut.queryCallForward(getConditionFromCFReason(commandInterfaceCFReason), null, resp);
} catch (ImsException e) {
sendErrorResponse(onComplete, e);
}
} else if (onComplete != null) {
sendErrorResponse(onComplete);
}
}
public void queryCallForward(int condition, String number, Message result) {
......
synchronized(mLockObj) {
try {
// 对于不同的平台,miUt的实现也不一样,高通平台上其实现对象为org.codeaurora.ims.ImsUtImpl
int id = miUt.queryCallForward(condition, number);
......
mPendingCmds.put(Integer.valueOf(id), result);
} catch (RemoteException e) {
......
}
}
}
ImsUtImpl.java
public int queryCallForward(int condition, String number) {
return queryCFForServiceClass(condition, number, SERVICE_CLASS_VOICE);
}
/**
* Retrieves the configuration of the call forward for specified service class.
*/
public int queryCFForServiceClass(int condition, String number, int serviceClass) {
......
// 简单的reason值转换
if (condition == ImsUtInterface.CDIV_CF_UNCONDITIONAL) {
reason = CF_REASON_UNCONDITIONAL;
}
else if (condition == ImsUtInterface.CDIV_CF_BUSY) {
reason = CF_REASON_BUSY;
}
else if (condition == ImsUtInterface.CDIV_CF_NO_REPLY) {
reason = CF_REASON_NO_REPLY;
}
else if (condition == ImsUtInterface.CDIV_CF_NOT_REACHABLE) {
reason = CF_REASON_NOT_REACHABLE;
}
else if (condition == ImsUtInterface.CDIV_CF_ALL) {
reason = CF_REASON_ALL;
}
else if (condition == ImsUtInterface.CDIV_CF_ALL_CONDITIONAL) {
reason = CF_REASON_ALL_CONDITIONAL;
}
else if (condition == ImsUtInterface.CDIV_CF_NOT_LOGGED_IN) {
//TODO: NOT SUPPORTED CURRENTLY.
// It's only supported in the IMS service (CS does not define it).
// IR.92 recommends that an UE activates both the CFNRc and the CFNL
// (CDIV using condition not-registered) to the same target.
reason = -1;
}
else {
Log.e(this, "Invalid condition for queryCallForward.");
return -1;
}
// 调用ImsSenderRxr.java的接口进行查询
mCi.queryCallForwardStatus(reason,
serviceClass,
number,
mHandler.obtainMessage(EVENT_QUERY_CF, id, 0, this));
return id;
}
查询完成后的处理:
ImsUtImpl.java
private class ImsUtImplHandler extends Handler {
public void handleMessage(Message msg) {
......
case EVENT_QUERY_CF:
ar = (AsyncResult) msg.obj;
if (ar != null) {
......
if (ar.exception != null) {
Log.e(this, "Query CF error");
......
}
else if (ar.result != null) {
if (ar.result instanceof ImsCallForwardTimerInfo[]) {
Log.i(this, "Handle CFUT response");
handleCFUTResponse(ar, msg); // CFUT的处理
return;
}
......
for (int i = 0; i < cfInfoList.length; i++) {
cfInfo = cfInfoList[i];
callForwardInfo = new ImsCallForwardInfo();
// 判断查询到的状态
if (cfInfo.status == 1) {
callForwardInfo.mStatus = 1; // Enabled
}
else if (cfInfo.status == 0) {
callForwardInfo.mStatus = 0; // Disabled
}
else {
badCfResponse = true;
Log.e(this, "Bad status in Query CF response.");
}
// 判断查询类型
if (cfInfo.reason == CF_REASON_UNCONDITIONAL) {
callForwardInfo.mCondition = ImsUtInterface.CDIV_CF_UNCONDITIONAL;
}
else if (cfInfo.reason == CF_REASON_BUSY) {
callForwardInfo.mCondition = ImsUtInterface.CDIV_CF_BUSY;
}
else if (cfInfo.reason == CF_REASON_NO_REPLY) {
callForwardInfo.mCondition = ImsUtInterface.CDIV_CF_NO_REPLY;
// Time present only in this case.
callForwardInfo.mTimeSeconds = cfInfo.timeSeconds;
}
else if (cfInfo.reason == CF_REASON_NOT_REACHABLE) {
callForwardInfo.mCondition = ImsUtInterface.CDIV_CF_NOT_REACHABLE;
}
else if (cfInfo.reason == CF_REASON_ALL) {
callForwardInfo.mCondition = ImsUtInterface.CDIV_CF_ALL;
}
else if (cfInfo.reason == CF_REASON_ALL_CONDITIONAL) {
callForwardInfo.mCondition = ImsUtInterface.CDIV_CF_ALL_CONDITIONAL;
}
else {
badCfResponse = true;
Log.e(this, "Bad reason in Query CF response.");
}
......
}
// 回调ImsUtListenerProxy.utConfigurationCallForwardQueried()
mListenerProxy.utConfigurationCallForwardQueried((IImsUt)ar.userObj,
msg.arg1,
callForwardInfoList);
}
else {
Log.e(this, "Null response received for Query CF!");
......
}
}
break;
......
}
}
ImsUtListenerProxy.java
public void utConfigurationCallForwardQueried(final IImsUt ut,
final int id,
final ImsCallForwardInfo[] cfInfo) {
if (mListener != null) {
final Runnable r = new Runnable() {
@Override
public void run() {
try {
// 继续回调
mListener.utConfigurationCallForwardQueried(ut, id, cfInfo);
} catch (Throwable t) {
handleError(t, "utConfigurationCallForwardQueried()");
}
}
};
startThread(r);
}
}
接下来我们看看ImsUtListenerProxy.mListener是在哪里进行注册的。
在ImsUtImpl.java中我们找到了注册的地方。
private ImsUtListenerProxy mListenerProxy = new ImsUtListenerProxy();
public void setListener(IImsUtListener listener) {
mContext.enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "setListener");
mListenerProxy.mListener = listener;
}
在ImsUt的构造方法中,我们发现,原来注册的Listener是IImsUtListenerProxy。
public ImsUt(IImsUt iUt) {
miUt = iUt;
if (miUt != null) {
try {
miUt.setListener(new IImsUtListenerProxy());
} catch (RemoteException e) {
}
}
}
因此,ImsUtListenerProxy.utConfigurationCallForwardQueried()会调用到:
ImsUt.java
private class IImsUtListenerProxy extends IImsUtListener.Stub {
public void utConfigurationCallForwardQueried(IImsUt ut,
int id, ImsCallForwardInfo[] cfInfo) {
Integer key = Integer.valueOf(id);
synchronized(mLockObj) {
// mPendingCmds.get(key)的值可在ImsPhone.getCallForwardingOption()中找到:
// resp = obtainMessage(EVENT_GET_CALL_FORWARD_DONE, onComplete);
sendSuccessReport(mPendingCmds.get(key), cfInfo);
mPendingCmds.remove(key);
}
}
}
接下来就是发送EVENT_GET_CALL_FORWARD_DONE消息给ImsPhone处理。
private void sendSuccessReport(Message result, Object ssInfo) {
if (result == null) {
return;
}
AsyncResult.forMessage(result, ssInfo, null);
result.sendToTarget();
}
ImsPhone处理EVENT_GET_CALL_FORWARD_DONE消息。
public void handleMessage(Message msg) {
case EVENT_GET_CALL_FORWARD_DONE:
CallForwardInfo[] cfInfos = null;
if (ar.exception == null) {
// 处理CF查询结果,对于CFU,会把结果保存到SharedPreferences及SIM卡
cfInfos = handleCfQueryResult((ImsCallForwardInfo[])ar.result);
}
// 发送消息EVENT_QUERY_CF_COMPLETE给ImsPhoneMmiCode
sendResponse((Message) ar.userObj, cfInfos, ar.exception);
break;
}
ImsPhoneMmiCode处理消息EVENT_QUERY_CF_COMPLETE。
handleMessage (Message msg) {
......
case EVENT_QUERY_CF_COMPLETE:
ar = (AsyncResult) (msg.obj);
onQueryCfComplete(ar);
break;
......
}
private void onQueryCfComplete(AsyncResult ar) {
StringBuilder sb = new StringBuilder(getScString());
sb.append("\n");
if (ar.exception != null) {
......
} else {
......
if (infos.length == 0) {
// 查询结果为空
......
} else {
// 将结果转换为SpannableStringBuilder对象
SpannableStringBuilder tb = new SpannableStringBuilder();
......
for (int serviceClassMask = 1
; serviceClassMask <= SERVICE_CLASS_MAX
; serviceClassMask <<= 1
) {
for (int i = 0, s = infos.length; i < s ; i++) {
if ((serviceClassMask & infos[i].serviceClass) != 0) {
tb.append(makeCFQueryResultMessage(infos[i],
serviceClassMask));
tb.append("\n");
}
}
}
sb.append(tb);
}
mState = State.COMPLETE;
}
mMessage = sb;
mPhone.onMMIDone(this); // 又回到ImsPhone进行处理
}
在ImsPhone中,继续向上通知监听者。
public void onMMIDone(ImsPhoneMmiCode mmi) {
/* Only notify complete if it's on the pending list.
* Otherwise, it's already been handled (eg, previously canceled).
* The exception is cancellation of an incoming USSD-REQUEST, which is
* not on the list.
*/
if (mPendingMMIs.remove(mmi) || mmi.isUssdRequest()) {
mMmiCompleteRegistrants.notifyRegistrants(
new AsyncResult(null, mmi, null));
}
}
通知会经由CallManager到PhoneGlobal,然后再调用PhoneUtils.displayMMIComplete()把结果显示出来。
private void onMMIComplete(AsyncResult r) {
MmiCode mmiCode = (MmiCode) r.result;
// 显示提示信息
PhoneUtils.displayMMIComplete(mmiCode.getPhone(), getInstance(), mmiCode, null, null);
}
接下来再接着ImsPhoneMmiCode.processCode()来看看设置CF的流程。
设置CallForwarding
这里跟查询CF状态类似,先判断CallForwarding的action及reason是否有效,然后再调用
ImsUt.updateCallForward()进行设置。
ImsPhone.java
public void setCallForwardingOption(int commandInterfaceCFAction,
int commandInterfaceCFReason,
String dialingNumber,
int serviceClass,
int timerSeconds,
Message onComplete) {
if ((isValidCommandInterfaceCFAction(commandInterfaceCFAction)) &&
(isValidCommandInterfaceCFReason(commandInterfaceCFReason))) {
Message resp;
Cf cf = new Cf(dialingNumber,
(commandInterfaceCFReason == CF_REASON_UNCONDITIONAL ? true : false),
onComplete);
resp = obtainMessage(EVENT_SET_CALL_FORWARD_DONE,
isCfEnable(commandInterfaceCFAction) ? 1 : 0, 0, cf);
try {
ImsUtInterface ut = mCT.getUtInterface();
// 这里跟查询时有点不同,注意下面方法的最后一个参数,查询时传入的是resp,
// 而这里传入的是onComplete,这回造成设置完成后上报消息时,不会有EVENT_SET_CALL_FORWARD_DONE消息,
// 不过,有的手机厂可能会修改为resp,这样可以进行一些额外的处理。
ut.updateCallForward(getActionFromCFAction(commandInterfaceCFAction),
getConditionFromCFReason(commandInterfaceCFReason),
dialingNumber,
serviceClass,
timerSeconds,
onComplete);
......
}
ImsUt.java
public void updateCallForward(int action, int condition, String number,
int serviceClass, int timeSeconds, Message result) {
......
synchronized(mLockObj) {
try {
// 高通平台上miUt为org.codeaurora.ims.ImsUtImpl的对象
int id = miUt.updateCallForward(action, condition, number, serviceClass, timeSeconds);
......
mPendingCmds.put(Integer.valueOf(id), result);
} catch (RemoteException e) {
......
}
}
ImsUtImpl.java
public int updateCallForward(int action, int condition, String number, int serviceClass,
int timeSeconds) {
mContext.enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, "updateCallForward");
......
// 调用ImsSenderRxr.java的接口进行设置
mCi.setCallForward(action,
condition,
serviceClass,
number,
timeSeconds,
mHandler.obtainMessage(EVENT_UPDATE_CF, id, 0, this));
return id;
}
设置完成后的处理:
private class ImsUtImplHandler extends Handler {
public void handleMessage(Message msg) {
....
case EVENT_UPDATE_CF:
case EVENT_UPDATE_CW:
ar = (AsyncResult) msg.obj;
if (ar != null) {
......
if (ar.exception != null) {
......
}
else {
Log.i(this, "Success callback called for msg.what= "
+ msg.what);
mListenerProxy.utConfigurationUpdated((IImsUt) ar.userObj, msg.arg1);
}
}
break;
......
}
}
关于这里的Listener的回调流程,之前在查询CF时已经进行了分析,最终会调到:
ImsUt.java
private class IImsUtListenerProxy extends IImsUtListener.Stub {
public void utConfigurationUpdated(IImsUt ut, int id) {
Integer key = Integer.valueOf(id);
synchronized(mLockObj) {
// 发送EVENT_SET_CFF_COMPLETE消息
sendSuccessReport(mPendingCmds.get(key));
mPendingCmds.remove(key);
}
}
}
ImsPhoneMmiCode.java中处理EVENT_SET_CFF_COMPLETE消息。
handleMessage (Message msg) {
......
case EVENT_SET_CFF_COMPLETE:
ar = (AsyncResult) (msg.obj);
/*
* msg.arg1 = 1 means to set unconditional voice call forwarding
* msg.arg2 = 1 means to enable voice call forwarding
*/
if ((ar.exception == null) && (msg.arg1 == 1)) {
boolean cffEnabled = (msg.arg2 == 1);
if (mIccRecords != null) {
// 对于CFU,会把结果保存到SharedPreferences及SIM卡
mPhone.setVoiceCallForwardingFlag(1, cffEnabled, mDialingNumber);
}
}
onSetComplete(msg, ar);
break;
......
}
private void onSetComplete(Message msg, AsyncResult ar){
StringBuilder sb = new StringBuilder(getScString());
sb.append("\n");
// 根据action获取相应的显示字符串
if (ar.exception != null) {
......
} else if (isActivate()) {
mState = State.COMPLETE;
if (mIsCallFwdReg) {
sb.append(mContext.getText(
com.android.internal.R.string.serviceRegistered));
} else {
sb.append(mContext.getText(
com.android.internal.R.string.serviceEnabled));
}
} else if (isDeactivate()) {
mState = State.COMPLETE;
sb.append(mContext.getText(
com.android.internal.R.string.serviceDisabled));
} else if (isRegister()) {
mState = State.COMPLETE;
sb.append(mContext.getText(
com.android.internal.R.string.serviceRegistered));
} else if (isErasure()) {
mState = State.COMPLETE;
sb.append(mContext.getText(
com.android.internal.R.string.serviceErased));
} else {
mState = State.FAILED;
sb.append(mContext.getText(
com.android.internal.R.string.mmiError));
}
mMessage = sb;
mPhone.onMMIDone(this);
}
再往后的流程就和查询CF的流程相同了。