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

拨打紧急号码的流程,以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值,
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值

在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);
    }
    }
    注意到一点,两种连接 一个是IMS connection 一个是GSM connection。(这个我并不确定,求高手补充)
    Emergency Number 拨打后
         
         
         
         
    07-23 13:06:06.950 4171-4171/com.android.phone D/Telephony TelephonyConnectionService: Adding IMS connection to conference controller: [TelephonyConnection objId:612117509 type:ims state:STATE_INITIALIZING capabilities:[Capabilities: SUPPORT_HOLD MUTE] address:[c751ce91522518e4613bf659f18549fefbb76424] originalConnection:[ImsPhoneConnection objId: 489542255 address:[601ca99d55f00a2e8e736676b606a4d31d374fdd] ImsCall:com.android.ims.ImsCall@137a9b7c] partOfConf:N]OriginalConnection is[ImsPhoneConnection objId: 489542255 address:[601ca99d55f00a2e8e736676b606a4d31d374fdd] ImsCall:com.android.ims.ImsCall@137a9b7c]
    普通号码
         
         
         
         
    07-24 14:25:16.036 3846-3846/? D/Telephony TelephonyConnectionService: Adding GSM connection to conference controller: [TelephonyConnection objId:870157190 type:gsm state:STATE_INITIALIZING capabilities:[Capabilities: SUPPORT_HOLD MUTE] address:[ea5d6942dec97279729796b6e1627dc33a42a105] originalConnection: incoming: false state: DIALING post dial state: NOT_STARTED partOfConf:N]OriginalConnection is incoming: false state: DIALING post dial state: NOT_STARTED
    之后应该是一样的了。

    你可能感兴趣的:(源码,mo,EmergencyCall)