android N 撥打電話流程(MO)
本流程圖基於MTK平臺 Android 7.0,撥打的普通電話,本流程只作為溝通學習使用
整體流程圖
流程中部分重點知識
packages-apps目錄
- dialer應用的DialpadFragment.onClick中,通過使用者輸入號碼並點選撥號按鈕(R.id.dialpad_floating_action_button)發起MO
- 在handleDialButtonPressed方法裡面會判斷輸入框中是否含有號碼,然後通過IntentUtil構造一個intent,並通過startActivityWithErrorToast啟動intent,這裡會判斷 mProhibitedPhoneNumberRegexp = getResources().getString(R.string.config_prohibited_phone_number_regexp);`是否含有預設拒絕撥打的號碼,如果含有彈出含有“Can\’t call this number”字串的DialogFragment提示使用者。
- 在startActivityWithErrorToast中,會獲取當前觸控式螢幕幕的位置資訊,並存入intent中,後續啟動incallUI的時候可能會使用
Point touchPoint = TouchPointManager.getInstance().getPoint();
if (touchPoint.x != 0 || touchPoint.y != 0) {
Bundle extras;
// Make sure to not accidentally clobber any existing extras
if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) {
extras = intent.getParcelableExtra(
TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
} else {
extras = new Bundle();
}
extras.putParcelable(TouchPointManager.TOUCH_POINT, touchPoint);
intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
} - 在TelecomUtil的這個方法中判斷是否有許可權撥打號碼,包括判斷是否是預設的dialer,和是否有”android.permission.CALL_PHONE”;這個許可權
public static boolean hasCallPhonePermission(Context context) {
return isDefaultDialer(context)
|| hasPermission(context, Manifest.permission.CALL_PHONE);
}
packages-services-Telecomm目錄
TelecomServiceImpl.placeCall
- 這裡會做一些檢查,比如:許可權檢查和包檢查,即使call 的許可權是關閉的也會走這裡,因為一些特殊的通話,比如:緊急通話,在後面的UserCallIntentProcessor中會把不是緊急號碼切許可權是關閉的通話給結束。
final boolean hasCallAppOp = mAppOpsManager.noteOp(AppOpsManager.OP_CALL_PHONE,
Binder.getCallingUid(), callingPackage) == AppOpsManager.MODE_ALLOWED;
final boolean hasCallPermission = mContext.checkCallingPermission(CALL_PHONE) ==
PackageManager.PERMISSION_GRANTED;
UserCallIntentProcessor
processIntent
public void processIntent(Intent intent, String callingPackageName,
boolean canCallNonEmergency) {
// Ensure call intents are not processed on devices that are not capable of calling.
if (!isVoiceCapable()) {//通過com.android.internal.R.bool.config_voice_capable判斷是否支援通話,比如:流量卡就不支援通話,並不會啟動incallUI
return;
}
String action = intent.getAction();
if (Intent.ACTION_CALL.equals(action) ||
Intent.ACTION_CALL_PRIVILEGED.equals(action) ||
Intent.ACTION_CALL_EMERGENCY.equals(action)) {
processOutgoingCallIntent(intent, callingPackageName, canCallNonEmergency);
}
}
public static final String ACTION_DIAL = "android.intent.action.DIAL";//普通電話進入dialer介面的撥號盤
public static final String ACTION_CALL = "android.intent.action.CALL";//只能撥打普通電話不能撥打緊急號碼,M即以上必須有android.Manifest.permission#CALL_PHONE許可權
public static final String ACTION_CALL_EMERGENCY = "android.intent.action.CALL_EMERGENCY"; //只能撥打緊急號碼的action
public static final String ACTION_CALL_PRIVILEGED = "android.intent.action.CALL_PRIVILEGED";//可以撥打任意型別電話的action
processOutgoingCallIntent
- 判斷是正常的通話型別是否是SIP phone
if (!PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
handle = Uri.fromParts(PhoneNumberUtils.isUriNumber(uriString) ?
PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, uriString, null);//構造intent的data
}
public static boolean isUriNumber(String number) {
// Note we allow either "@" or "%40" to indicate a URI, in case
// the passed-in string is URI-escaped. (Neither "@" nor "%40"
// will ever be found in a legal PSTN number.)
return number != null && (number.contains("@") || number.contains("%40"));
}
- 判斷是否有DISALLOW_OUTGOING_CALLS的限制,如果撥打的是emergency call則忽略這個限制,如果撥打的正常號碼且具有這個限制,或者許可權沒有通過,不允許打正常的電話,則彈出錯誤的對話方塊 like:“Only emergency calls are allowed.”or “This application cannot make outgoing calls without the Phone permission.”
- 如果是video_call並且打的是emergency_call則需要將video的狀態改成voice來打emergency_call
intent.putExtra(CallIntentProcessor.KEY_IS_PRIVILEGED_DIALER,
isDefaultOrSystemDialer(callingPackageName));//判斷是否是預設或者系統的dialer應用,如果是才可以打emergency_call
PrimaryCallReceiver
public void onReceive(Context context, Intent intent) {
Log.startSession("PCR.oR");
synchronized (getTelecomSystem().getLock()) {
if (!ExtensionManager.getCallMgrExt()
.shouldPreventVideoCallIfLowBattery(context, intent)) {//如果是video_call的話,判斷當前的電量是否處於低電模式,現在預設是返回false,後續會根據不同專案要求做定製
getTelecomSystem().getCallIntentProcessor().processIntent(intent);
}
}
Log.endSession();
}
CallIntentProcessor
processOutgoingCallIntent
- 這個方法裡面主要還是重新拿出intent裡面的資料,並判斷當前是否是主要的dialer,是否sip_phone,是否有phone賬戶,是否是video狀態,用哪一張卡打,是否是IMS通話請求,是否是會議通話請求,根據這些判斷再重新構造intent的Extras,然後通過前面得到的狀態建立一個call,如果是conference的話直接建立連結,如果不是則繼續往下執行
if (intent.hasExtra(TelecomUtils.EXTRA_SLOT)) {
int slotId = intent.getIntExtra(TelecomUtils.EXTRA_SLOT, -1);
phoneAccountHandle = TelecomUtils
.getPhoneAccountHandleWithSlotId(context, slotId, phoneAccountHandle);//如果有多張卡的話會拿到重那張卡撥打號碼
}
// Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
Call call = callsManager
.startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser);//通過這裡來啟動incallUI
NewOutgoingCallIntentBroadcaster
processIntent
- 先判斷是否是VoicemailNumber,然後再得到格式化號碼包括大小寫轉換成數字,分隔符的分離,判斷是否是潛在的緊急號碼,再將action為ACTION_CALL_PRIVILEGED型別的通話變成ACTION_CALL_EMERGENCY或者ACTION_CALL,
* - 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) 如果是撥打緊急號碼的話會直接呼叫placeOutgoingCall,否則會執行後面的流程傳送廣播。
boolean isUriNumber = mPhoneNumberUtilsAdapter.isUriNumber(number);//判斷通話型別,是否是sip_phone
if (!isUriNumber) {
number = mPhoneNumberUtilsAdapter.convertKeypadLettersToDigits(number);//將號碼字串的字母轉換成數字
number = mPhoneNumberUtilsAdapter.stripSeparators(number);//將號碼中的分割符分離
}
final boolean isPotentialEmergencyNumber = isPotentialEmergencyNumber(number);//判斷是否是潛在的緊急號碼
Log.v(this, "isPotentialEmergencyNumber = %s", isPotentialEmergencyNumber);
rewriteCallIntentAction(intent, isPotentialEmergencyNumber);//將ACTION_CALL_PRIVILEGED變成ACTION_CALL_EMERGENCY或者ACTION_CALL
CallsManager
placeOutgoingCall
- 這裡主要是對CDMA模式的call的廣播,然後就是根據一些狀態判斷speaker的開關,是否是緊急號碼的判斷,然後就是開始建立連結
CreateConnectionProcessor
attemptNextPhoneAccount
if (mCallResponse != null && attempt != null) {
Log.i(this, "Trying attempt %s", attempt);
PhoneAccountHandle phoneAccount = attempt.connectionManagerPhoneAccount;
mService = mRepository.getService(phoneAccount.getComponentName(),
phoneAccount.getUserHandle());//得到建立連結的service
if (mService == null) {
Log.i(this, "Found no connection service for attempt %s", attempt);
attemptNextPhoneAccount();
} else {
mCall.setConnectionManagerPhoneAccount(attempt.connectionManagerPhoneAccount);//設定phoneaccount的ConnectionManager
mCall.setTargetPhoneAccount(attempt.targetPhoneAccount);
mCall.setConnectionService(mService);//設定service
setTimeoutIfNeeded(mService, attempt);
mService.createConnection(mCall, this);//建立連結
}
}
ConnectionServiceWrapper
createConnection
- 通過binder建立連結,如果建立成功,先會判斷callid是否為null以應對某些特定情況下連結建立成功前呼叫就已經斷開了,這種情況下會列印錯誤log和DisconnectCause.ERROR的response返回,然後繼續往下執行建立連結。
try {
/// M: For VoLTE @{
boolean isConferenceDial = call.isConferenceDial();
if (isConferenceDial) {
logOutgoing("createConference(%s) via %s.", call, getComponentName());
mServiceInterface.createConference(//會議連結
call.getConnectionManagerPhoneAccount(),
callId,
new ConnectionRequest(
call.getTargetPhoneAccount(),
call.getHandle(),
extras,
call.getVideoState(),
callId),
call.getConferenceParticipants(),
call.isIncoming());
} else {
mServiceInterface.createConnection(//普通連結
call.getConnectionManagerPhoneAccount(),
callId,
new ConnectionRequest(
call.getTargetPhoneAccount(),
call.getHandle(),
extras,
call.getVideoState(),
callId),
call.shouldAttachToExistingConnection(),
call.isUnknown());
}
/// @}
} catch (RemoteException e) {
Log.e(this, e, "Failure to createConnection -- %s", getComponentName());
mPendingResponses.remove(callId).handleCreateConnectionFailure(
new DisconnectCause(DisconnectCause.ERROR, e.toString()));
}
packages-services-Telephony目錄
TelephonyConnectionService
onCreateOutgoingConnection
這裡會做一系列的判斷:
- 撥打號碼是否為null,撥打電話的賬戶是否存在
- 判斷是否是ECC重連(緊急號碼相關),如果是將voiceemail的通話型別轉換成tel,否則判斷當前號碼是否和*228正則表示式匹配,如果匹配就拒絕此次連結,因為這些特殊的數字用於OTASP,如果不禁用可能將LTE鎖定到3G網路下。
- 判斷phone是否為null,如果為null並且config_checkSimStateBeforeOutgoingCall值為true就會去檢查當前SIM卡的狀態,並確定是否彈出PIN碼的輸入框
- 判斷飛航模式是否開啟
- 判斷如果是撥打緊急號碼且當前處於4G only資料鏈接狀態,者取消這次連結
- 判斷當前網路是否註冊成功
- 判斷當前是否是緊急號碼並處於isInEcm模式,如果撥打的不是緊急號碼,但是當前處於ECM模式,則KEY_ALLOW_NON_EMERGENCY_CALLS_IN_ECM_BOOL這個bool值覺得是否結束當前連結(ECM模式是指緊急呼叫回撥模式,一般GSM網路不支援,CDMA網路支援)
- 對service狀態的判斷,處理不部分異常資訊
- 如果是videocall則判斷TTY是否開啟,如果開啟則斷開連結(TTY聾啞人專用模式)
- 連結的初始化,number,video狀態這些的配置
// If configured, reject attempts to dial numbers matching this pattern.
private static final Pattern CDMA_ACTIVATION_CODE_REGEX_PATTERN =
Pattern.compile("\\*228[0-9]{0,2}");
/// M: For ECC change feature @{
if (isEmergencyNumber) {
if (mSwitchPhoneHelper == null) {
mSwitchPhoneHelper = new SwitchPhoneHelper(this, number);
}
if (mSwitchPhoneHelper.needToPrepareForDial()) {
mSwitchPhoneHelper.prepareForDial(
new SwitchPhoneHelper.Callback() {
@Override
public void onComplete(boolean success) {
if (connection.getState() == Connection.STATE_DISCONNECTED) {
Log.d(this, "prepareForDial, connection disconnect");
} else if (success) {
Log.d(this, "startTurnOnRadio");
startTurnOnRadio(connection, request, number);
} else {
/// M: CC: ECC Retry @{
// Assume only one ECC exists
// Not trigger retry since Modem fails to
// power on should be a bug
if (TelephonyConnectionServiceUtil.getInstance()
.isEccRetryOn()) {
Log.d(this, "ECC Retry : clear ECC param");
TelephonyConnectionServiceUtil.getInstance()
.clearEccRetryParams();
}
/// @}
Log.d(this, "prepareForDial, failed to turn on radio");
connection.setDisconnected(
DisconnectCauseUtil.toTelecomDisconnectCause(
android.telephony.DisconnectCause.POWER_OFF,
"Failed to turn on radio."));
connection.destroy();
}
}
});
/// @}
} else {
/// M: CC: ECC Retry @{
if (!TelephonyConnectionServiceUtil.getInstance().isEccRetryOn()) {
Log.d(this, "ECC Retry : set param with Intial ECC.");
TelephonyConnectionServiceUtil.getInstance().setEccRetryParams(
request,
phone.getPhoneId());
}
/// @}
if (useEmergencyCallHelper) {
if (mEmergencyCallHelper == null) {
mEmergencyCallHelper = new EmergencyCallHelper(this);
}
final Phone eccPhone = phone;
mEmergencyCallHelper.startTurnOnRadioSequence(eccPhone,
new EmergencyCallHelper.Callback() {
@Override
public void onComplete(boolean isRadioReady) {
if (connection.getState() == Connection.STATE_DISCONNECTED) {
Log.d(this, "onCreateOutgoingConnection,"
+ " connection disconnected");
// If the connection has already been disconnected,
// do nothing.
} else if (isRadioReady) {
///M: 4G data only @{
if (TelephonyConnectionServiceUtil.getInstance()
.isDataOnlyMode(eccPhone)) {
Log.d(this, "startTurnOnRadioSequence, 4G data only");
/// M: CC: ECC Retry @{
// Assume only one ECC exists
if (TelephonyConnectionServiceUtil.getInstance()
.isEccRetryOn()) {
Log.d(this, "ECC Retry : clear ECC param");
TelephonyConnectionServiceUtil.getInstance()
.clearEccRetryParams();
}
/// @}
connection.setDisconnected(
DisconnectCauseUtil.toTelecomDisconnectCause(
android.telephony.DisconnectCause
.OUTGOING_CANCELED, null));
connection.destroy();
return;
}
/// @}
connection.setInitialized();
placeOutgoingConnection(connection, eccPhone, request);
} else {
/// M: CC: ECC Retry @{
// Assume only one ECC exists
// Not trigger retry since Modem fails to
// power on should be a bug
if (TelephonyConnectionServiceUtil.getInstance()
.isEccRetryOn()) {
Log.d(this, "ECC Retry : clear ECC param");
TelephonyConnectionServiceUtil.getInstance()
.clearEccRetryParams();
}
/// @}
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);
}
}
} else {
placeOutgoingConnection(connection, phone, request);
}
frameworks-opt-telephony目錄
dial
- 這裡會判斷是否是IMScall,GSMcall,WiFicall,緊急通話,然後通過不同的phone繼續下發dial指令
public Connection dial(String dialString, UUSInfo uusInfo, int videoState, Bundle intentExtras)
throws CallStateException {
if (!isPhoneTypeGsm() && uusInfo != null) {
throw new CallStateException("Sending UUS information NOT supported in CDMA!");
}
boolean isEmergency = PhoneNumberUtils.isEmergencyNumber(dialString);
Phone imsPhone = mImsPhone;
CarrierConfigManager configManager =
(CarrierConfigManager) mContext.getSystemService(Context.CARRIER_CONFIG_SERVICE);
boolean alwaysTryImsForEmergencyCarrierConfig = configManager.getConfigForSubId(getSubId())
.getBoolean(CarrierConfigManager.KEY_CARRIER_USE_IMS_FIRST_FOR_EMERGENCY_BOOL);
boolean imsUseEnabled = isImsUseEnabled()
&& imsPhone != null
&& (imsPhone.isVolteEnabled() || imsPhone.isWifiCallingEnabled() ||
(imsPhone.isVideoEnabled() && VideoProfile.isVideo(videoState)))
&& (imsPhone.getServiceState().getState() == ServiceState.STATE_IN_SERVICE);
boolean useImsForEmergency = imsPhone != null
&& isEmergency
&& alwaysTryImsForEmergencyCarrierConfig
&& ImsManager.isNonTtyOrTtyOnVolteEnabled(mContext)
&& (imsPhone.getServiceState().getState() != ServiceState.STATE_POWER_OFF);
/// M: @{
if (!isPhoneTypeGsm()) {
useImsForEmergency = false; //TODO: remove this workaround for ECC fail
}
/// @}
String dialPart = PhoneNumberUtils.extractNetworkPortionAlt(PhoneNumberUtils.
stripSeparators(dialString));
boolean isUt = (dialPart.startsWith("*") || dialPart.startsWith("#"))
&& dialPart.endsWith("#");
boolean useImsForUt = imsPhone != null && imsPhone.isUtEnabled();
if (DBG) {
logd("imsUseEnabled=" + imsUseEnabled
+ ", useImsForEmergency=" + useImsForEmergency
+ ", useImsForUt=" + useImsForUt
+ ", isUt=" + isUt
+ ", imsPhone=" + imsPhone
+ ", imsPhone.isVolteEnabled()="
+ ((imsPhone != null) ? imsPhone.isVolteEnabled() : "N/A")
+ ", imsPhone.isVowifiEnabled()="
+ ((imsPhone != null) ? imsPhone.isWifiCallingEnabled() : "N/A")
+ ", imsPhone.isVideoEnabled()="
+ ((imsPhone != null) ? imsPhone.isVideoEnabled() : "N/A")
+ ", imsPhone.getServiceState().getState()="
+ ((imsPhone != null) ? imsPhone.getServiceState().getState() : "N/A"));
}
Phone.checkWfcWifiOnlyModeBeforeDial(mImsPhone, mContext);
/// M: should be removed later, just for debug @{
Rlog.w(LOG_TAG, "IMS: imsphone = " + imsPhone + "isEmergencyNumber = "
+ PhoneNumberUtils.isEmergencyNumber(dialString));
if (imsPhone != null) {
Rlog.w(LOG_TAG, "service state = " + imsPhone.getServiceState().getState());
}
/// @}
if ((imsUseEnabled && (!isUt || useImsForUt)) || useImsForEmergency) {
/// M: CC: Check GSM call state to avoid InCallMMI dispatching to IMS @{
// [ALPS02516173],[ALPS02615800]
if (isInCSCall()) {
if (DBG) Rlog.d(LOG_TAG, "has CS Call. Don't try IMS PS Call!");
} else {
/// @}
try {
/// M: ALPS02137073 3G VT Refactory
if (videoState == VideoProfile.STATE_AUDIO_ONLY) {
if (DBG) Rlog.d(LOG_TAG, "Trying IMS PS call");
return imsPhone.dial(dialString, uusInfo, videoState, intentExtras);
} else {
if (SystemProperties.get("persist.mtk_vilte_support").equals("1")) {
if (DBG) {
Rlog.d(LOG_TAG, "Trying IMS PS video call");
}
return imsPhone.dial(dialString, uusInfo, videoState, intentExtras);
} else {
/// M: CC: For 3G VT only @{
if (DBG) {
Rlog.d(LOG_TAG, "Trying (non-IMS) CS video call");
}
return dialInternal(dialString, uusInfo, videoState, intentExtras);
/// @}
}
}
} catch (CallStateException e) {
if (DBG) logd("IMS PS call exception " + e +
"imsUseEnabled =" + imsUseEnabled + ", imsPhone =" + imsPhone);
if (!Phone.CS_FALLBACK.equals(e.getMessage())) {
CallStateException ce = new CallStateException(e.getMessage());
ce.setStackTrace(e.getStackTrace());
throw ce;
}
}
/// M: CC: Check GSM call state to avoid InCallMMI dispatching to IMS @{
}
/// @}
}
/// M: CC: FTA requires call should be dialed out even out of service @{
if (SystemProperties.getInt("gsm.gcf.testmode", 0) != 2) {
if (mSST != null && mSST.mSS.getState() == ServiceState.STATE_OUT_OF_SERVICE
&& mSST.mSS.getDataRegState() != ServiceState.STATE_IN_SERVICE
&& !isEmergency) {
throw new CallStateException("cannot dial in current state");
}
}
/// @}
if (DBG) logd("Trying (non-IMS) CS call");
if (isPhoneTypeGsm()) {
/// M: CC: For 3G VT only @{
//return dialInternal(dialString, null, VideoProfile.STATE_AUDIO_ONLY, intentExtras);
return dialInternal(dialString, null, videoState, intentExtras);
/// @}
} else {
return dialInternal(dialString, null, videoState, intentExtras);
}
}
dialInternal
這裡主要是判斷是否是MMI code如果是的話就只執行MMIcode的指令,否則繼續往下下發撥號動作。
@Override
protected Connection dialInternal(String dialString, UUSInfo uusInfo, int videoState,
Bundle intentExtras)
throws CallStateException {
// Need to make sure dialString gets parsed properly
/// M: Ignore stripping for VoLTE SIP uri. @{
// String newDialString = PhoneNumberUtils.stripSeparators(dialString);
String newDialString = dialString;
if (!PhoneNumberUtils.isUriNumber(dialString)) {
// Need to make sure dialString gets parsed properly
newDialString = PhoneNumberUtils.stripSeparators(dialString);
}
/// @}
if (isPhoneTypeGsm()) {
// handle in-call MMI first if applicable
if (handleInCallMmiCommands(newDialString)) {
return null;
}
// Only look at the Network portion for mmi
String networkPortion = PhoneNumberUtils.extractNetworkPortionAlt(newDialString);
GsmMmiCode mmi =
GsmMmiCode.newFromDialString(networkPortion, this, mUiccApplication.get());
if (DBG) logd("dialing w/ mmi '" + mmi + "'...");
if (mmi == null) {
/// M: CC: For 3G VT only @{
//return mCT.dial(newDialString, uusInfo, intentExtras);
if (videoState == VideoProfile.STATE_AUDIO_ONLY) {
return mCT.dial(newDialString, uusInfo, intentExtras);
} else {
if (!is3GVTEnabled()) {
throw new CallStateException("cannot vtDial for non-3GVT-capable device");
}
return mCT.vtDial(newDialString, uusInfo, intentExtras);
}
/// @}
} else if (mmi.isTemporaryModeCLIR()) {
/// M: CC: For 3G VT only @{
//return mCT.dial(mmi.mDialingNumber, mmi.getCLIRMode(), uusInfo, intentExtras);
if (videoState == VideoProfile.STATE_AUDIO_ONLY) {
return mCT.dial(mmi.mDialingNumber, mmi.getCLIRMode(), uusInfo, intentExtras);
} else {
if (!is3GVTEnabled()) {
throw new CallStateException("cannot vtDial for non-3GVT-capable device");
}
return mCT.vtDial(mmi.mDialingNumber, mmi.getCLIRMode(), uusInfo, intentExtras);
}
/// @}
} else {
mPendingMMIs.add(mmi);
/// M: @{
Rlog.d(LOG_TAG, "dialInternal: " + dialString + ", mmi=" + mmi);
dumpPendingMmi();
/// @}
mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
try {
mmi.processCode();
} catch (CallStateException e) {
//do nothing
}
// FIXME should this return null or something else?
return null;
}
} else {
return mCT.dial(newDialString);
}
}
GsmCdmaCallTracker
dial
- 清楚所有已斷開的連結,並通知call的狀態改變
- 如果當前有一個call處於前臺,我們需要將它hold住,並通過一個500毫秒的延時來執行hold這個操作,直到我們收到EVENT_SWITCH_RESULT執行結束的標記,如果不延時在多方會議通話的時候可能出現問題
- 設定為mute
public synchronized Connection dial(String dialString, int clirMode, UUSInfo uusInfo,
Bundle intentExtras)
throws CallStateException {
// note that this triggers call state changed notif
clearDisconnected();
if (!canDial()) {
throw new CallStateException("cannot dial in current state");
}
String origNumber = dialString;
dialString = convertNumberIfNecessary(mPhone, dialString);
// The new call must be assigned to the foreground call.
// That call must be idle, so place anything that's
// there on hold
if (mForegroundCall.getState() == GsmCdmaCall.State.ACTIVE) {
// this will probably be done by the radio anyway
// but the dial might fail before this happens
// and we need to make sure the foreground call is clear
// for the newly dialed connection
/// M: CC: Proprietary CRSS handling @{
mWaitingForHoldRequest.set();
/// @}
switchWaitingOrHoldingAndActive();
// This is a hack to delay DIAL so that it is sent out to RIL only after
// EVENT_SWITCH_RESULT is received. We've seen failures when adding a new call to
// multi-way conference calls due to DIAL being sent out before SWITCH is processed
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// do nothing
}
// Fake local state so that
// a) foregroundCall is empty for the newly dialed connection
// b) hasNonHangupStateChanged remains false in the
// next poll, so that we don't clear a failed dialing call
fakeHoldForegroundBeforeDial();
}
if (mForegroundCall.getState() != GsmCdmaCall.State.IDLE) {
//we should have failed in !canDial() above before we get here
throw new CallStateException("cannot dial in current state");
}
mPendingMO = new GsmCdmaConnection(mPhone, checkForTestEmergencyNumber(dialString),
this, mForegroundCall);
mHangupPendingMO = false;
if ( mPendingMO.getAddress() == null || mPendingMO.getAddress().length() == 0
|| mPendingMO.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0) {
// Phone number is invalid
mPendingMO.mCause = DisconnectCause.INVALID_NUMBER;
/// M: CC: Proprietary CRSS handling @{
mWaitingForHoldRequest.reset();
/// @}
// handlePollCalls() will notice this call not present
// and will mark it as dropped.
pollCallsWhenSafe();
} else {
// Always unmute when initiating a new call
setMute(false);
/// M: CC: Proprietary CRSS handling @{
//mCi.dial(mPendingMO.getAddress(), clirMode, uusInfo, obtainCompleteMessage());
if (!mWaitingForHoldRequest.isWaiting()) {
/// M: CC: Proprietary ECC handling@{
/// M: CC: ECC Retry @{
if (PhoneNumberUtils.isEmergencyNumber(mPhone.getSubId(), dialString)
/// @}
&& !PhoneNumberUtils.isSpecialEmergencyNumber(dialString)) {
int serviceCategory = PhoneNumberUtils.getServiceCategoryFromEcc(dialString);
mCi.setEccServiceCategory(serviceCategory);
mCi.emergencyDial(mPendingMO.getAddress(), clirMode, uusInfo,
obtainCompleteMessage(EVENT_DIAL_CALL_RESULT));
/// @}
} else {
mCi.dial(mPendingMO.getAddress(), clirMode, uusInfo,
obtainCompleteMessage(EVENT_DIAL_CALL_RESULT));
}
} else {
mWaitingForHoldRequest.set(mPendingMO.getAddress(), clirMode, uusInfo);
}
/// @}
}
if (mNumberConverted) {
mPendingMO.setConverted(origNumber);
mNumberConverted = false;
}
updatePhoneState();
mPhone.notifyPreciseCallStateChanged();
return mPendingMO;
}
Log資訊
Line 10357: 12-19 09:17:32.193 I/Telecom ( 988): CallIntentProcessor: onReceive - isUnknownCall: false: PCR.oR@AOY
Line 10473: 12-19 09:17:32.288 V/Telecom ( 988): CallsManager: startOutgoingCall found accounts = [PhoneAccountHandle{TelephonyConnectionService, 89860115881029413909, UserHandle{0}}]: PCR.oR@AOY
Line 10553: 12-19 09:17:32.351 V/Telecom ( 988): NewOutgoingCallIntentBroadcaster: Processing call intent in OutgoingCallIntentBroadcaster.: PCR.oR@AOY
Line 10872: 12-19 09:17:32.406 V/Telecom ( 988): NewOutgoingCallIntentBroadcaster: isPotentialEmergencyNumber = false: PCR.oR@AOY
Line 10877: 12-19 09:17:32.407 I/Telecom ( 988): NewOutgoingCallIntentBroadcaster: Sending NewOutgoingCallBroadcast for [TC@2, CONNECTING, null, tel:10010, A, childs(0), has_parent(false), [Capabilities:], [Properties:]] to UserHandle{0}: PCR.oR@AOY
Line 10878: 12-19 09:17:32.408 V/Telecom ( 988): NewOutgoingCallIntentBroadcaster: Broadcasting intent: Intent { act=android.intent.action.NEW_OUTGOING_CALL flg=0x10000000 (has extras) }.: PCR.oR@AOY
Line 11902: 12-19 09:17:33.374 V/Telecom ( 988): NewOutgoingCallBroadcastIntentReceiver: onReceive: Intent { act=android.intent.action.NEW_OUTGOING_CALL flg=0x10000010 (has extras) }: NOCBIR.oR@AOw
Line 11903: 12-19 09:17:33.374 I/Telecom ( 988): NewOutgoingCallBroadcastIntentReceiver: Received new-outgoing-call-broadcast for [TC@2, CONNECTING, null, tel:10010, A, childs(0), has_parent(false), [Capabilities:], [Properties:]] with data 10010: NOCBIR.oR@AOw
Line 11950: 12-19 09:17:33.426 V/Telecom ( 988): NewOutgoingCallBroadcastIntentReceiver: Call number unmodified after new outgoing call intent broadcast.: NOCBIR.oR@AOw
Line 11951: 12-19 09:17:33.426 D/Telecom ( 988): CallsManager: broadcastCallPlacedIntent Entry: NOCBIR.oR@AOw
Line 11954: 12-19 09:17:33.427 D/Telecom ( 988): CallsManager: Creating a new outgoing call with handle: tel:10010: NOCBIR.oR@AOw
Line 12034: 12-19 09:17:33.495 D/Telecom ( 988): CallsManager: [TC@2, CONNECTING, null, tel:10010, A, childs(0), has_parent(false), [Capabilities:], [Properties:]] Starting with speakerphone because car is docked.: NOCBIR.oR@AOw
Line 12038: 12-19 09:17:33.498 V/Telecom ( 988): CreateConnectionProcessor: process: NOCBIR.oR@AOw
Line 12044: 12-19 09:17:33.513 I/Telecom ( 988): PhoneAccountRegistrar: SimCallManager queried, returning: null: NOCBIR.oR@AOw
Line 12045: 12-19 09:17:33.513 V/Telecom ( 988): CreateConnectionProcessor: setConnectionManager, not changing: NOCBIR.oR@AOw
Line 12046: 12-19 09:17:33.514 V/Telecom ( 988): CreateConnectionProcessor: attemptNextPhoneAccount: NOCBIR.oR@AOw
Line 12047: 12-19 09:17:33.515 I/Telecom ( 988): CreateConnectionProcessor: Trying attempt CallAttemptRecord(PhoneAccountHandle{TelephonyConnectionService, 89860115881029413909, UserHandle{0}},PhoneAccountHandle{TelephonyConnectionService, 89860115881029413909, UserHandle{0}}): NOCBIR.oR@AOw
Line 12050: 12-19 09:17:33.517 D/Telecom ( 988): ConnectionServiceWrapper: createConnection([TC@2, CONNECTING, com.android.phone/com.android.services.telephony.TelephonyConnectionService, tel:10010, A, childs(0), has_parent(false), [Capabilities:], [Properties:]]) via ComponentInfo{com.android.phone/com.android.services.telephony.TelephonyConnectionService}.: NOCBIR.oR@AOw
Line 12051: 12-19 09:17:33.517 D/Telecom ( 988): ConnectionServiceWrapper: bind(): NOCBIR.oR@AOw
Line 12073: 12-19 09:
相關推薦
android N 撥打電話流程(MO)
本流程圖基於MTK平臺 Android 7.0,撥打的普通電話,本流程只作為溝通學習使用
整體流程圖
流程中部分重點知識
packages-apps目錄
dialer應用的DialpadFragment.onClick中,通過使用者
Android5.1 Telephony流程分析——撥打電話流程(MO CALL)
本文程式碼以MTK平臺Android 5.1為分析物件,與Google原生AOSP有些許差異,請讀者知悉。
此圖主要是根據Android原始碼撥打電話流程來繪製,記錄了電話撥打的主要過程:
參考部
Android App應用啟動流程(二)
還回到上一篇結束的地方:
void startSpecificActivityLocked(ActivityRecord r,
boolean andResume, boolean checkConfig) {
// Is this
可贏Android SDK的接入流程(Unity)
可贏現階段只提供了android端的SDK, 為了要整合到Unity工程中,只能在androidStudio中先整合可贏SDK,並匯出.aar檔案;把.aar檔案放入Unity工程中,在C#程式碼中呼叫.aar檔案相應的介面,並打包apk檔案(不能用Unity直
(O)Telephony分析之通話流程分析(二)撥打電話流程分析(上)
撥打電話,是從通話的撥號盤開始的,而撥號盤的介面是在DialpadFragment,因此,需要從這個地方進行分析一.撥號盤介面撥號流程
public void onClick(View view) {
......
if (resId == R.id.dia
android呼叫撥打電話(包括執行時許可權)
/**
* 撥打電話(跳轉到撥號介面,使用者手動點選撥打)
*
* @param phoneNum 電話號碼
*/
public void diallP
理解Android進程創建流程(轉)
object c mman appdata sel failed scrip sca emp 不足
/frameworks/base/core/java/com/android/internal/os/
- ZygoteInit.java
- Zygote
理解Android線程創建流程(轉)
ttr cal 創建失敗 指向 ear long readn nbsp bar
/android/libcore/libart/src/main/java/java/lang/Thread.java
/art/runtime/native/java_lang_Thread
Android系統啟動流程(一)解析init進程啟動過程
option 寫入 android change failed miss 通知 target sna 前言
作為“Android框架層”這個大系列中的第一個系列,我們首先要了解的是Android系統啟動流程,在這個流程中會涉及到很多重要的知識點,這個系列我們就來一一講解它們
Android View原理解析之測量流程(measure)
提示:本文的原始碼均取自Android 7.0(API 24)
前言
自定義View是Android進階路線上必須攻克的難題,而在這之前就應該先對View的工作原理有一個系統的理解。本系列將分為4篇部落格進行講解,本文主要對View的測量流程進行講解。相關內容如
Android View原理解析之佈局流程(layout)
提示:本文的原始碼均取自Android 7.0(API 24)
前言
自定義View是Android進階路線上必須攻克的難題,而在這之前就應該先對View的工作原理有一個系統的理解。本系列將分為4篇部落格進行講解,本文主要對View的佈局流程進行講解。相關內容如
WebRTC搭建流程(五)Android端除錯
Android端除錯
(一)下載Demo
編譯需要linux+翻牆+16G的原始碼下載,太坑了,以後編譯,直接下載別人編好的應用demo
下載地址
(二)demo中需要修改
所有demo中的域名修改成
Android系統啟動流程(一)解析init程序啟動過程
前言
作為“Android框架層”這個大系列中的第一個系列,我們首先要了解的是Android系統啟動流程,在這個流程中會涉及到很多重要的知識點,這個系列我們就來一一講解它們,這一篇我們就來學習init程序。
1.init簡介
init程序是An
Android系統啟動流程(二)解析Zygote程序啟動過程
前言
上一篇文章我們分析了init程序的啟動過程,啟動過程中主要做了三件事,其中一件就是建立了Zygote程序,那麼Zygote程序是什麼,它做了哪些事呢?這篇文章會給你這些問題的答案。
1.Zygote簡介
在Android系統中,DVM(D
Android系統啟動流程(四)Launcher啟動過程與系統啟動流程
相關文章 Android系統架構與系統原始碼目錄 Android系統啟動流程(一)解析init程序啟動過程 Android系統啟動流程(二)解析Zygote程序啟動過程 Android系統啟動流程(三)解析SyetemServer程序啟動過程
前言
Android系統啟動流程(三)解析SyetemServer程序啟動過程
相關文章 Android系統架構與系統原始碼目錄 Android系統啟動流程(一)解析init程序啟動過程 Android系統啟動流程(二)解析Zygote程序啟動過程
前言 上一篇我們學習了Zygote程序,並且知道Zygote程序啟動了SyetemServ
Android按鍵訊息傳播流程(WindowManagerService.java)
主要涉及的檔案有:
WindowManagerService.java frameworks\base\services\java\com\android\server\
PhoneWindow.java frameworks\p
Android Sprd省電管理(二)應用省電模式設定流程
在Android Sprd省電管理(一)appPowerSaveConfig.xml,我們介紹了appPowerSaveConfig.xml的主要引數的意義,這一篇我們介紹下,怎麼設定應用的各種省電模式
首先看SprdManageApplications這個類
以鎖屏清理為例,點選開關
Android Gallery3D原始碼學習總結(三)——Cache快取及資料處理流程
第一,在應用程式中有三個執行緒存在:主執行緒(隨activity的宣告週期啟動銷燬)、feed初始化執行緒(進入程式時只執行一次,用於載入相簿初始資訊)、feed監聽執行緒(一直在跑,監聽相簿和相片的變更)。
第二,不考慮CacheService 啟動的主要流程歸納如下:
1
Android Gallery3d原始碼學習總結(二)——繪製流程drawThumbnails
此函式控制相簿表格頁、相片表格頁、時間分類表格頁的展示,非常重要。以下以相簿表格頁為例進行講解,其他的就舉一反三吧。準備輸入引數
final GridDrawables drawables = mDrawables;
final DisplayList d