目录
紧急电话的流程
针对紧急电话的几种特殊情况代码处理
ACTION_CALL和ACTION_CALL_EMERGENCY
在MO流程中call action有三种,在NewOutgoingCallIntentBroadcaster中对三种action进行了处理,我们先从源码的注释中先看一下他们分别用在什么时候
181 /**
182 * Processes the supplied intent and starts the outgoing call broadcast process relevant to the
183 * intent.
184 *
185 * This method will handle three kinds of actions:
186 *
187 * - CALL (intent launched by all third party dialers)
188 * - CALL_PRIVILEGED (intent launched by system apps e.g. system Dialer, voice Dialer)
189 * - CALL_EMERGENCY (intent launched by lock screen emergency dialer)
190 *
191 * @return {@link DisconnectCause#NOT_DISCONNECTED} if the call succeeded, and an appropriate
192 * {@link DisconnectCause} if the call did not, describing why it failed.
193 */
ACTION_CALL:所有第三方应用拨号发出的action
ACTION_CALL_PRIVILEGED:系统app发出的action
ACTION_CALL_EMERGENCY:在锁屏界面点击紧急拨号出现的拨号盘发出的action
实际上一般系统拨号盘发出的action是ACTION_CALL,锁屏界面紧急拨号的action是ACTION_CALL_EMERGENCY。我们看下代码是如何针对两者处理的:
/packages/services/Telecomm/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
195 public int processIntent() {
......
240 boolean callImmediately = false;
......
261 final boolean isPotentialEmergencyNumber = isPotentialEmergencyNumber(number);
....
267 if (Intent.ACTION_CALL.equals(action)) {
268 if (isPotentialEmergencyNumber) {
//如果是第三方应用播出的紧急号码,就会跳转到系统拨号盘拨号
269 if (!mIsDefaultOrSystemPhoneApp) {
270 Log.w(this, "Cannot call potential emergency number %s with CALL Intent %s "
271 + "unless caller is system or default dialer.", number, intent);
272 launchSystemDialer(intent.getData());
273 return DisconnectCause.OUTGOING_CANCELED;
274 } else {
275 callImmediately = true;
276 }
277 }
278 } else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) {
279 if (!isPotentialEmergencyNumber) {
280 Log.w(this, "Cannot call non-potential-emergency number %s with EMERGENCY_CALL "
281 + "Intent %s.", number, intent);
282 return DisconnectCause.OUTGOING_CANCELED;
283 }
284 callImmediately = true;
285 } else {
286 Log.w(this, "Unhandled Intent %s. Ignoring and not placing call.", intent);
287 return DisconnectCause.INVALID_NUMBER;
288 }
......
//可以看到,系统应用的拨号更快,直接调用placeOutgoingCallImmediately,而普通拨号还需要发送广播
300 if (callImmediately) {
301 boolean speakerphoneOn = mIntent.getBooleanExtra(
302 TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false);
303 int videoState = mIntent.getIntExtra(
304 TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
305 VideoProfile.STATE_AUDIO_ONLY);
306 placeOutgoingCallImmediately(mCall, callingAddress, null,
307 speakerphoneOn, videoState);
308
309 // Don't return but instead continue and send the ACTION_NEW_OUTGOING_CALL broadcast
310 // so that third parties can still inspect (but not intercept) the outgoing call. When
311 // the broadcast finally reaches the OutgoingCallBroadcastReceiver, we'll know not to
312 // initiate the call again because of the presence of the EXTRA_ALREADY_CALLED extra.
313 }
314
315 if (sendNewOutgoingCallBroadcast) {
316 UserHandle targetUser = mCall.getInitiatingUser();
317 Log.i(this, "Sending NewOutgoingCallBroadcast for %s to %s", mCall, targetUser);
318 broadcastIntent(intent, number, !callImmediately, targetUser);
319 }
......
这段代码主要有两点:
- 第三方应用拨打紧急号码,会跳转到拨号盘
- 紧急电话的拨号流程更简单,会省略很多步骤
判断号码是不是紧急号码isPotentialEmergencyNumber
紧急通话流程走到类NewOutgoingCallIntentBroadcaster的processIntent方法中,主要是通过isPotentialEmergencyNumber判断了号码是不是紧急号码。我们跟踪这个方法到PhoneNumberUtils类的isEmergencyNumberInternal方法
1971 private static boolean isEmergencyNumberInternal(int subId, String number,
1972 String defaultCountryIso,
1973 boolean useExactMatch) {
1974 // If the number passed in is null, just return false:
1975 if (number == null) return false;
1976
1977 // If the number passed in is a SIP address, return false, since the
1978 // concept of "emergency numbers" is only meaningful for calls placed
1979 // over the cell network.
1980 // (Be sure to do this check *before* calling extractNetworkPortionAlt(),
1981 // since the whole point of extractNetworkPortionAlt() is to filter out
1982 // any non-dialable characters (which would turn '[email protected]'
1983 // into '911', for example.))
1984 if (isUriNumber(number)) {
1985 return false;
1986 }
1987
1988 // Strip the separators from the number before comparing it
1989 // to the list.
1990 number = extractNetworkPortionAlt(number);
1991
1992 String emergencyNumbers = "";
1993 int slotId = SubscriptionManager.getSlotIndex(subId);
1994
1995 // retrieve the list of emergency numbers
1996 // check read-write ecclist property first
1997 String ecclist = (slotId <= 0) ? "ril.ecclist" : ("ril.ecclist" + slotId);
1998
1999 emergencyNumbers = SystemProperties.get(ecclist, "");
2000
2001 Rlog.d(LOG_TAG, "slotId:" + slotId + " subId:" + subId + " country:"
2002 + defaultCountryIso + " emergencyNumbers: " + emergencyNumbers);
2003
2004 if (TextUtils.isEmpty(emergencyNumbers)) {
2005 // then read-only ecclist property since old RIL only uses this
2006 emergencyNumbers = SystemProperties.get("ro.ril.ecclist");
2007 }
2008
2009 if (!TextUtils.isEmpty(emergencyNumbers)) {
2010 // searches through the comma-separated list for a match,
2011 // return true if one is found.
2012 for (String emergencyNum : emergencyNumbers.split(",")) {
2013 // It is not possible to append additional digits to an emergency number to dial
2014 // the number in Brazil - it won't connect.
2015 if (useExactMatch || "BR".equalsIgnoreCase(defaultCountryIso)) {
2016 if (number.equals(emergencyNum)) {
2017 return true;
2018 }
2019 } else {
2020 if (number.startsWith(emergencyNum)) {
2021 return true;
2022 }
2023 }
2024 }
2025 // no matches found against the list!
2026 return false;
2027 }
2028
2029 Rlog.d(LOG_TAG, "System property doesn't provide any emergency numbers."
2030 + " Use embedded logic for determining ones.");
2031
2032 // If slot id is invalid, means that there is no sim card.
2033 // According spec 3GPP TS22.101, the following numbers should be
2034 // ECC numbers when SIM/USIM is not present.
2035 emergencyNumbers = ((slotId < 0) ? "112,911,000,08,110,118,119,999" : "112,911");
2036
2037 for (String emergencyNum : emergencyNumbers.split(",")) {
2038 if (useExactMatch) {
2039 if (number.equals(emergencyNum)) {
2040 return true;
2041 }
2042 } else {
2043 if (number.startsWith(emergencyNum)) {
2044 return true;
2045 }
2046 }
2047 }
2048
2049 // No ecclist system property, so use our own list.
2050 if (defaultCountryIso != null) {
2051 ShortNumberInfo info = ShortNumberInfo.getInstance();
2052 if (useExactMatch) {
2053 return info.isEmergencyNumber(number, defaultCountryIso);
2054 } else {
2055 return info.connectsToEmergencyNumber(number, defaultCountryIso);
2056 }
2057 }
2058
2059 return false;
2060 }
在此函数中会从4个来源读取紧急号码:
1)SIM卡(ril.ecclist, ril.ecclist2) 。
读取的是存储在SIM卡中的紧急号码,仅当插了SIM卡的情况下才可能读出数据。无法向此property中写入数据,因为每次开机都会重新改写此property。
TODO//Log中打印的号码为
2) RILD(ro.ril.ecclist)。
这是一个只读的property,里面写入的紧急号码为112和911,此property内容无法更改。
TODO//Log中打印的号码为
3)数组(emergencyNumList)
有卡情况下数组为"112,911",无卡"112,911,000,08,110,118,119,999"。如果需要添加紧急号码,可以写入此数组。
4)根据国家码读取文件
这个目录下不同的国家码与文件相对应,从文件中读取紧急拨号的号码
/external/libphonenumber/libphonenumber/src/com/google/i18n/phonenumbers/data/
例如中国
//TODO 看log 拨打120试试
有sim卡和无sim卡的情况拨打紧急电话
飞行模式下拨打紧急号码
如果打开了飞行模式拨打紧急电话,会关闭再去拨打
269 public Connection onCreateOutgoingConnection(
270 PhoneAccountHandle connectionManagerPhoneAccount,
271 final ConnectionRequest request) {
272 Log.i(this, "onCreateOutgoingConnection, request: " + request);
.........
367
368 final boolean isEmergencyNumber =
369 PhoneNumberUtils.isLocalEmergencyNumber(this, numberToDial);
370
371
372 final boolean isAirplaneModeOn = Settings.Global.getInt(getContentResolver(),
373 Settings.Global.AIRPLANE_MODE_ON, 0) > 0;
374 //如果飞行模式打开,这个值为true
375 boolean needToTurnOnRadio = (isEmergencyNumber && (!isRadioOn() || isAirplaneModeOn))
376 || isRadioPowerDownOnBluetooth();
377
378 if (needToTurnOnRadio) {
379 final Uri resultHandle = handle;
380 // By default, Connection based on the default Phone, since we need to return to Telecom
381 // now.
382 final int originalPhoneType = PhoneFactory.getDefaultPhone().getPhoneType();
383 final Connection resultConnection = getTelephonyConnection(request, numberToDial,
384 isEmergencyNumber, resultHandle, PhoneFactory.getDefaultPhone());
385 if (mRadioOnHelper == null) {
386 mRadioOnHelper = new RadioOnHelper(this);
387 }
//尝试去关闭飞行模式
388 mRadioOnHelper.triggerRadioOnAndListen(new RadioOnStateListener.Callback() {
389 @Override
//关闭成功后回调,继续拨打紧急电话
390 public void onComplete(RadioOnStateListener listener, boolean isRadioReady) {
391 handleOnComplete(isRadioReady, isEmergencyNumber, resultConnection, request,
392 numberToDial, resultHandle, originalPhoneType);
393 }
394
395 @Override
396 public boolean isOkToCall(Phone phone, int serviceState) {
397 if (isEmergencyNumber) {
398 // We currently only look to make sure that the radio is on before dialing.
399 // We should be able to make emergency calls at any time after the radio has
400 // been powered on and isn't in the UNAVAILABLE state, even if it is
401 // reporting the OUT_OF_SERVICE state.
402 return (phone.getState() == PhoneConstants.State.OFFHOOK)
403 || phone.getServiceStateTracker().isRadioOn();
404 } else {
405 // It is not an emergency number, so wait until we are in service and ready
406 // to make calls. This can happen when we power down the radio on bluetooth
407 // to save power on watches.
408 return (phone.getState() == PhoneConstants.State.OFFHOOK)
409 || serviceState == ServiceState.STATE_IN_SERVICE;
410 }
411 }
412 });
413 // Return the still unconnected GsmConnection and wait for the Radios to boot before
414 // connecting it to the underlying Phone.
415 return resultConnection;
416 } else {
417 if (!canAddCall() && !isEmergencyNumber) {
418 Log.d(this, "onCreateOutgoingConnection, cannot add call .");
419 return Connection.createFailedConnection(
420 new DisconnectCause(DisconnectCause.ERROR,
421 getApplicationContext().getText(
422 R.string.incall_error_cannot_add_call),
423 getApplicationContext().getText(
424 R.string.incall_error_cannot_add_call),
425 "Add call restricted due to ongoing video call"));
426 }
427
428 // Get the right phone object from the account data passed in.
429 final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber);
430 Connection resultConnection = getTelephonyConnection(request, numberToDial,
431 isEmergencyNumber, handle, phone);
432 // If there was a failure, the resulting connection will not be a TelephonyConnection,
433 // so don't place the call!
434 if (resultConnection instanceof TelephonyConnection) {
435 if (request.getExtras() != null && request.getExtras().getBoolean(
436 TelecomManager.EXTRA_USE_ASSISTED_DIALING, false)) {
437 ((TelephonyConnection) resultConnection).setIsUsingAssistedDialing(true);
438 }
439 placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request);
440 }
441 return resultConnection;
442 }
443 }
这边就是对飞行模式下拨打紧急电话进行处理,尝试关闭飞行模式再拨打电话
Gsm和Cdma拨打紧急打电话的差别
382 private Connection dial(String dialString, int clirMode) throws CallStateException {
383 // note that this triggers call state changed notif
384 clearDisconnected();
385
386 if (!canDial()) {
387 throw new CallStateException("cannot dial in current state");
388 }
389
390 TelephonyManager tm =
391 (TelephonyManager) mPhone.getContext().getSystemService(Context.TELEPHONY_SERVICE);
392 String origNumber = dialString;
393 String operatorIsoContry = tm.getNetworkCountryIsoForPhone(mPhone.getPhoneId());
394 String simIsoContry = tm.getSimCountryIsoForPhone(mPhone.getPhoneId());
395 boolean internationalRoaming = !TextUtils.isEmpty(operatorIsoContry)
396 && !TextUtils.isEmpty(simIsoContry)
397 && !simIsoContry.equals(operatorIsoContry);
398 if (internationalRoaming) {
399 if ("us".equals(simIsoContry)) {
400 internationalRoaming = internationalRoaming && !"vi".equals(operatorIsoContry);
401 } else if ("vi".equals(simIsoContry)) {
402 internationalRoaming = internationalRoaming && !"us".equals(operatorIsoContry);
403 }
404 }
405 if (internationalRoaming) {
406 dialString = convertNumberIfNecessary(mPhone, dialString);
407 }
408
409 boolean isPhoneInEcmMode = mPhone.isInEcm();
//Cdma和Gsm验证紧急电话方式不一样
410 boolean isEmergencyCall =
411 PhoneNumberUtils.isLocalEmergencyNumber(mPhone.getContext(), dialString);
412
413 // Cancel Ecm timer if a second emergency call is originating in Ecm mode
//在Ecm模式(锁屏界面拨打紧急电话)下拨打紧急电话,不显示通话时间栏
414 if (isPhoneInEcmMode && isEmergencyCall) {
415 handleEcmTimer(GsmCdmaPhone.CANCEL_ECM_TIMER);
416 }
417
418 // The new call must be assigned to the foreground call.
419 // That call must be idle, so place anything that's
420 // there on hold
421 if (mForegroundCall.getState() == GsmCdmaCall.State.ACTIVE) {
422 return dialThreeWay(dialString);
423 }
424
425 mPendingMO = new GsmCdmaConnection(mPhone, checkForTestEmergencyNumber(dialString),
426 this, mForegroundCall, isEmergencyCall);
427 mHangupPendingMO = false;
428
429 if ( mPendingMO.getAddress() == null || mPendingMO.getAddress().length() == 0
430 || mPendingMO.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0 ) {
431 // Phone number is invalid
432 mPendingMO.mCause = DisconnectCause.INVALID_NUMBER;
433
434 // handlePollCalls() will notice this call not present
435 // and will mark it as dropped.
436 pollCallsWhenSafe();
437 } else {
438 // Always unmute when initiating a new call
439 setMute(false);
440
441 // Check data call
//Cdma拨打紧急电话会关闭数据连接
442 disableDataCallInEmergencyCall(dialString);
443
444 // In Ecm mode, if another emergency call is dialed, Ecm mode will not exit.
//Cdma只能在紧急模式下拨打紧急号码
445 if(!isPhoneInEcmMode || (isPhoneInEcmMode && isEmergencyCall)) {
446 mCi.dial(mPendingMO.getAddress(), clirMode, obtainCompleteMessage());
447 } else {
448 mPhone.exitEmergencyCallbackMode();
449 mPhone.setOnEcbModeExitResponse(this,EVENT_EXIT_ECM_RESPONSE_CDMA, null);
450 mPendingCallClirMode=clirMode;
451 mPendingCallInEcm=true;
452 }
453 }
454
455 if (mNumberConverted) {
456 mPendingMO.setConverted(origNumber);
457 mNumberConverted = false;
458 }
459
460 updatePhoneState();
461 mPhone.notifyPreciseCallStateChanged();
462
463 return mPendingMO;
464 }
总结一下:cdma拨打紧急电话有以下几点
Cdma和Gsm验证紧急电话方式不一样
//TODO,等找到CDMA手机再去验证一下
Cdma只能在在Ecm模式(锁屏界面拨打紧急电话)下拨打紧急号码
cdma拨打紧急电话,不显示通话时间栏
Cdma拨打紧急电话会关闭数据连接
拨打紧急电话过程中Call的状态变化
对比log分析