您的位置:首页 > 移动开发 > Android开发

Android L 5.0 上紧急电话EmergencyCall与普通电话在MO流程上的区别

2015-08-05 14:37 501 查看
转载请注明出处:http://blog.csdn.net/aaa111/article/details/47296721

拨打紧急号码的流程,以112为例。

参考了有SIM卡,无SIM卡,和有SIM卡但开启飞行模式的log,将分析结果简单整理如下。
首先紧急电话的拨打流程也是一个MO流程,至于相对于普通号码在流程细节上有几处区别,那么我们以普通MO流程为参考,着重讲一下不同之处。 下面是一个MO的流程图:



在MO流程中call action有三种,我们先看一下他们分别用在什么时候,在NewOutgoingCallIntentBroadcaster.java中processIntent()方法前的注释中可以看到:

/**
* Processes the supplied intent and starts the outgoing call broadcast process relevant to the
* intent.
*
* This method will handle three kinds of actions:
*
* - CALL (intent launched by all third party dialers)
* - CALL_PRIVILEGED (intent launched by system apps e.g. system Dialer, voice Dialer)
* - CALL_EMERGENCY (intent launched by lock screen emergency dialer)
*
* @return {@link CallActivity#OUTGOING_CALL_SUCCEEDED} if the call succeeded, and an
*         appropriate {@link DisconnectCause} if the call did not, describing why it failed.
*/


CALL为第三方的拨号程序,CALL_PRIVILEGED是系统拨号程序,CALL_EMERGENCY是锁屏界面的emergency 拨号程序。

但是在系统拨号程序中拨入号码,系统是如何是辨认emergency number的呢?
往方法里面可以看到,代码中通过isPotentialEmergencyNumber()判断这个是不是Emergency Number(注:是否是Emergency Number 是跟SIM卡以及国家有关的,好像是在PhoneNumberUtil中),如果是的话就去重写Intent 的Action值,
8 android/packages/services/Telecomm/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java   processIntent()

final boolean isPotentialEmergencyNumber = isPotentialEmergencyNumber(number);
Log.v(this, "isPotentialEmergencyNumber = %s", isPotentialEmergencyNumber);
 
rewriteCallIntentAction(intent, isPotentialEmergencyNumber);//紧急号码的话 重写action值为android.intent.action.CALL_EMERGENCY了


在isPotentialEmergencyNumber()方法前的注释中,我们可以看到,“in order to enforce the restriction that only the CALL_PRIVILEGED and CALL_EMERGENCY intents are allowed
to make emergency  calls.”
那我们是否得出结论第三方的拨号程序是不允许拨打紧急电话的?(five minutes later)试了某第三方拨号程序,拨打112的时候会跳转到系统dialer,不会直接拨出电话。
在rewriteCallIntentAction()方法中“ updating
action from CALL_PRIVILEGED to android.intent.action.CALL_EMERGENCY”

11 android/packages/services/Telecomm/src/com/android/server/telecom/CallsManager.java

void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo, boolean speakerphoneOn,
int videoState) {
if (call == null) {
// don't do anything if the call no longer exists
Log.i(this, "Canceling unknown call.");
return;
}
 
final Uri uriHandle = (gatewayInfo == null) ? handle : gatewayInfo.getGatewayAddress();
 
if (gatewayInfo == null) {
Log.i(this, "Creating a new outgoing call with handle: %s", Log.piiHandle(uriHandle));
} else {
Log.i(this, "Creating a new outgoing call with gateway handle: %s, original handle: %s",
Log.pii(uriHandle), Log.pii(handle));
}
 
call.setHandle(uriHandle);
call.setGatewayInfo(gatewayInfo);
call.setStartWithSpeakerphoneOn(speakerphoneOn);
call.setVideoState(videoState);
 
boolean isEmergencyCall = TelephonyUtil.shouldProcessAsEmergency(mContext,
call.getHandle());
if (isEmergencyCall) {
// Emergency -- CreateConnectionProcessor will choose accounts automatically
call.setTargetPhoneAccount(null);
}
 
if (call.getTargetPhoneAccount() != null || isEmergencyCall) {
if (!isEmergencyCall) {
updateLchStatus(call.getTargetPhoneAccount().getId());
}
// If the account has been set, proceed to place the outgoing call.
// Otherwise the connection will be initiated when the account is set by the user.
call.startCreateConnection(mPhoneAccountRegistrar);
}
}


13 android/packages/services/Telecomm/src/com/android/server/telecom/CreateConnectionProcessor.java

void process() {
Log.v(this, "process");
mAttemptRecords = new ArrayList<>();
if (mCall.getTargetPhoneAccount() != null) {
mAttemptRecords.add(new CallAttemptRecord(
mCall.getTargetPhoneAccount(), mCall.getTargetPhoneAccount()));
}
adjustAttemptsForConnectionManager();
adjustAttemptsForEmergency();
mAttemptRecordIterator = mAttemptRecords.iterator();
attemptNextPhoneAccount();
}


14 如果是一般的号码那么执行adjustAttemptsForEmergency()并不会对流程有什么影响,如果是紧急号码的话那么我们进来看看它做了什么。

// If we are possibly attempting to call a local emergency number, ensure that the
// plain PSTN connection services are listed, and nothing else.
private void adjustAttemptsForEmergency()  {
if (TelephonyUtil.shouldProcessAsEmergency(mContext, mCall.getHandle())) {
Log.i(this, "Emergency number detected");
mAttemptRecords.clear();
List<PhoneAccount> allAccounts = mPhoneAccountRegistrar.getAllPhoneAccounts();//获得所有账户(SIM/SIP/IMS等)
 
if (allAccounts.isEmpty()) {//如果是空的话
// If the list of phone accounts is empty at this point, it means Telephony hasn't
// registered any phone accounts yet. Add a fallback emergency phone account so
// that emergency calls can still go through. We create a new ArrayLists here just
// in case the implementation of PhoneAccountRegistrar ever returns an unmodifiable
// list.
allAccounts = new ArrayList<PhoneAccount>();
allAccounts.add(TelephonyUtil.getDefaultEmergencyPhoneAccount());
//如果是空的话,那么我么赋予一个默认的紧急拨号账户
}
 
 
// First, add SIM phone accounts which can place emergency calls.
for (PhoneAccount phoneAccount : allAccounts) {
if (phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS) &&
phoneAccount.hasCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION)) {
Log.i(this, "Will try PSTN account %s for emergency",
    //PSTN?
phoneAccount.getAccountHandle());
mAttemptRecords.add(
new CallAttemptRecord(
phoneAccount.getAccountHandle(),
phoneAccount.getAccountHandle()));
}
}
 
// Next, add the connection manager account as a backup if it can place emergency calls.
PhoneAccountHandle callManagerHandle = mPhoneAccountRegistrar.getSimCallManager();
if (callManagerHandle != null) {
PhoneAccount callManager = mPhoneAccountRegistrar
.getPhoneAccount(callManagerHandle);
if (callManager.hasCapabilities(PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS)) {
CallAttemptRecord callAttemptRecord = new CallAttemptRecord(callManagerHandle,
mPhoneAccountRegistrar.
getDefaultOutgoingPhoneAccount(mCall.getHandle().getScheme())
);
 
if (!mAttemptRecords.contains(callAttemptRecord)) {
Log.i(this, "Will try Connection Manager account %s for emergency",
callManager);
    mAttemptRecords.add(callAttemptRecord);
}
}
}
}
}


allAccounts.add(TelephonyUtil.getDefaultEmergencyPhoneAccount());如果phone
Account是空的(没有SIM/SIP等),那么则返回一个专用的EmergencyPhoneAccount

/**
* @return fallback {@link PhoneAccount} to be used by Telecom for emergency calls in the
* rare case that Telephony has not registered any phone accounts yet. Details about this
* account are not expected to be displayed in the UI, so the description, etc are not
* populated.
*/
static PhoneAccount getDefaultEmergencyPhoneAccount() {
return PhoneAccount.builder(DEFAULT_EMERGENCY_PHONE_ACCOUNT_HANDLE, "E")
.setCapabilities(PhoneAccount.CAPABILITY_SIM_SUBSCRIPTION |
PhoneAccount.CAPABILITY_CALL_PROVIDER |
PhoneAccount.CAPABILITY_PLACE_EMERGENCY_CALLS).build();
}


这个CAPABILITY_SIM_SUBSCRIPTION比较奇特啊,a built-in PSTN SIM subscription,一个内置的SIM账户?

/**
* Flag indicating that this {@code PhoneAccount} represents a built-in PSTN SIM
* subscription.
* <p>
* Only the Android framework can register a {@code PhoneAccount} having this capability.
* <p>
* See {@link #getCapabilities}
*/
public static final int CAPABILITY_SIM_SUBSCRIPTION = 0x4;


下面是几种情况下拨打112 ,phoneAccount.getAccountHandle());打印出来的结果多是不同的,具体后面的 5 E 3 2 代表的什么意思我也不知道。了解的大神,求解释!求补充!

C:\Users\admin\Desktop\MO_Log\112 with simcard.txt (3 hits)
07-23 13:04:52.020    4146-4146/com.android.server.telecom I/Telecom﹕ Call: setTargetPhoneAccount
ComponentInfo{com.android.phone/com.android.services.telephony.TelephonyConnectionService}, 5
C:\Users\admin\Desktop\MO_Log\112 with no sim card.txt (4 hits)
07-23 13:06:06.409    4146-4146/com.android.server.telecom I/Telecom﹕ Call: setTargetPhoneAccount
ComponentInfo{com.android.phone/com.android.services.telephony.TelephonyConnectionService}, E (这个稍后会变成-1)
C:\Users\admin\Desktop\MO_Log\112 airplane mode.txt (3 hits)
07-23 13:08:57.877    4146-4146/com.android.server.telecom I/Telecom﹕ Call: setTargetPhoneAccount
ComponentInfo{com.android.phone/com.android.services.telephony.TelephonyConnectionService}, 3
C:\Users\admin\Desktop\MO_Log\MO.txt (1 hit)
04-04 14:25:02.677: I/Telecom(4147): Call: setTargetPhoneAccount  
ComponentInfo{com.android.phone/com.android.services.telephony.TelephonyConnectionService}, 2


15 之后继续 attemptNextPhoneAccount

如果已经开启飞行模式会关闭飞行模式先 log:

770: 07-23 13:08:58.351    4171-4171/com.android.phone D/Telephony﹕ EmergencyCallHelper: EmergencyCallHelper constructor.
770: 07-23 13:08:58.351    4171-4171/com.android.phone D/Telephony﹕ EmergencyCallHelper: EmergencyCallHelper constructor.
772: 07-23 13:08:58.352    4171-4171/com.android.phone D/Telephony﹕ EmergencyCallHelper: startTurnOnRadioSequence
788: 07-23 13:08:58.358    4171-4171/com.android.phone D/Telephony﹕ EmergencyCallHelper: startSequenceInternal()
790: 07-23 13:08:58.358    4171-4171/com.android.phone D/Telephony﹕ EmergencyCallHelper: cleanup()
794: 07-23 13:08:58.358    4171-4171/com.android.phone D/Telephony﹕ EmergencyCallHelper: powerOnRadio().
798: 07-23 13:08:58.359    4171-4171/com.android.phone D/Telephony﹕ EmergencyCallHelper: ==> Turning off airplane mode.
1720: 07-23 13:08:58.787    4171-4171/com.android.phone D/Telephony﹕ EmergencyCallHelper: onServiceStateChanged(), new state = 1 1 home null null null  Unknown Unknown CSS not supported -1 -1 RoamInd=-1 DefRoamInd=-1 EmergOnly=false.
1722: 07-23 13:08:58.787    4171-4171/com.android.phone D/Telephony﹕ EmergencyCallHelper: onServiceStateChanged: not ready to call yet, keep waiting.
1976: 07-23 13:08:59.019    4171-4171/com.android.phone D/Telephony﹕ EmergencyCallHelper: onServiceStateChanged(), new state = 0 1 home CHN-UNICOM UNICOM 46001  GSM Unknown CSS supported -1 -1 RoamInd=-1 DefRoamInd=-1 EmergOnly=false.
1978: 07-23 13:08:59.020    4171-4171/com.android.phone D/Telephony﹕ EmergencyCallHelper: onServiceStateChanged: ok to call!
2160: 07-23 13:08:59.092    4171-4171/com.android.phone D/Telephony﹕ EmergencyCallHelper: cleanup()


具体流程不详述,打开飞行模式的入口代码如下,并且打开radio之后,同样执行了placeOutgoingConnection方法。

25 android/packages/services/Telephony/src/com/android/services/telephony/TelephonyConnectionService.java

@Override
public Connection onCreateOutgoingConnection(
PhoneAccountHandle connectionManagerPhoneAccount,
final ConnectionRequest request) {
...
boolean useEmergencyCallHelper = false;
if (isEmergencyNumber) {
mRequest = request;
if (state == ServiceState.STATE_POWER_OFF) {//如果radio没有开
useEmergencyCallHelper = true;
}
}
...
if (useEmergencyCallHelper) {
if (mEmergencyCallHelper == null) {
mEmergencyCallHelper = new EmergencyCallHelper(this);
}
mEmergencyCallHelper.startTurnOnRadioSequence(phone,//打开radio,进而如果是开启了飞行模式的话则关闭飞行模式
new EmergencyCallHelper.Callback() {
@Override
public void onComplete(boolean isRadioReady) {
if (connection.getState() == Connection.STATE_DISCONNECTED) {
// If the connection has already been disconnected, do nothing.
} else if (isRadioReady) {
connection.setInitialized();
placeOutgoingConnection(connection,//打开radio后
PhoneFactory.getPhone(getPhoneIdForECall()), request);
} else {
Log.d(this, "onCreateOutgoingConnection, failed to turn on radio");
connection.setDisconnected(
DisconnectCauseUtil.toTelecomDisconnectCause(
android.telephony.DisconnectCause.POWER_OFF,
"Failed to turn on radio."));
connection.destroy();
}
}
});
}else {
placeOutgoingConnection(connection, phone, request);//如果radio本来就可用的话
}
}


而紧急号码和非紧急号码之间的区别,在 placeOutgoingConnection()中的第二个参数,Phone

(飞行模式的时候会打印两次?TelephonyConnectionService: Voice phoneId in service = 0 preferred phoneId =0)

26 android/packages/services/Telephony/src/com/android/services/telephony/TelephonyConnectionService.java

private void placeOutgoingConnection(
TelephonyConnection connection, Phone phone, ConnectionRequest request) {
String number = connection.getAddress().getSchemeSpecificPart();
 
PhoneAccountHandle pHandle = TelecomAccountRegistry.makePstnPhoneAccountHandle(phone);
// For ECall handling on MSIM, till the request reaches here(i.e PhoneApp)
// we dont know on which phone account ECall can be placed, once after deciding
// the phone account for ECall we should inform Telecomm so that
// the proper sub information will be displayed on InCallUI.
if (!Objects.equals(pHandle, request.getAccountHandle())) {
Log.i(this, "setPhoneAccountHandle, account = " + pHandle);
connection.setPhoneAccountHandle(pHandle);
}
Bundle bundle = request.getExtras();
boolean isAddParticipant = (bundle != null) && bundle
.getBoolean(TelephonyProperties.ADD_PARTICIPANT_KEY, false);
Log.d(this, "placeOutgoingConnection isAddParticipant = " + isAddParticipant);
 
com.android.internal.telephony.Connection originalConnection;
try {
if (isAddParticipant) {
phone.addParticipant(number);
    return;
} else {
originalConnection = phone.dial(number, request.getVideoState(), bundle);
    //拨号
}
} catch (CallStateException e) {
Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e);
connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
android.telephony.DisconnectCause.OUTGOING_FAILURE,
e.getMessage()));
return;
}
 
if (originalConnection == null) {
int telephonyDisconnectCause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
// On GSM phones, null connection means that we dialed an MMI code
if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
Log.d(this, "dialed MMI code");
telephonyDisconnectCause = android.telephony.DisconnectCause.DIALED_MMI;
final Intent intent = new Intent(this, MMIDialogActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
startActivity(intent);
}
Log.d(this, "placeOutgoingConnection, phone.dial returned null");
connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
telephonyDisconnectCause, "Connection is null"));
} else {
connection.setOriginalConnection(originalConnection);
}
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: