android9.0紧急号码拨号流程

时间:2024-04-09 22:23:55

目录

紧急电话的流程

针对紧急电话的几种特殊情况代码处理

拨打紧急电话过程中Call的状态变化

对比log分析


紧急电话的流程

针对紧急电话的几种特殊情况代码处理

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        }
......

这段代码主要有两点:

  1. 第三方应用拨打紧急号码,会跳转到拨号盘
  2. 紧急电话的拨号流程更简单,会省略很多步骤

判断号码是不是紧急号码isPotentialEmergencyNumber

紧急通话流程走到类NewOutgoingCallIntentBroadcasterprocessIntent方法中,主要是通过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/

android9.0紧急号码拨号流程

例如中国

//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分析