android 8.0 MO 通话
本文是作为本人学习总结。
首先挂一下流程图。
通话相关界面的显示和控制从/packages/apps/Dialer目录下。
一 /packages/apps/Dialer 层
dial 从DialpadFragment 开始,DialpadFragment 显示一个12键拨号键盘。
在handleDialButtonPressed 方法中, 检查号码是否是被禁止的号码, 然后进一步处理。
public void onClick(View view) {
int resId = view.getId();
if (resId == R.id.dialpad_floating_action_button) {
view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
handleDialButtonPressed();
// 点击delete 键
} else if (resId == R.id.deleteButton) {
keyPressed(KeyEvent.KEYCODE_DEL);
//点击数字键
} else if (resId == R.id.digits) {
if (!isDigitsEmpty()) {
mDigits.setCursorVisible(true);
}
} else if (resId == R.id.dialpad_overflow) {
mOverflowPopupMenu.show();
} else {
LogUtil.w("DialpadFragment.onClick", "Unexpected event from: " + view);
return;
}
}
private void handleDialButtonPressed() {
if (isDigitsEmpty()) { // No number entered.
// 拨打最后通话的号码
handleDialButtonClickWithEmptyDigits();
} else {
final String number = mDigits.getText().toString();
// "persist.radio.otaspdial" is a temporary hack needed for one carrier's automated
// test equipment.
// TODO: clean it up.
// 检查是否是禁止拨出的号码
if (number != null
&& !TextUtils.isEmpty(mProhibitedPhoneNumberRegexp)
&& number.matches(mProhibitedPhoneNumberRegexp)) {
LogUtil.i(
"DialpadFragment.handleDialButtonPressed",
"The phone number is prohibited explicitly by a rule.");
if (getActivity() != null) {
DialogFragment dialogFragment =
ErrorDialogFragment.newInstance(R.string.dialog_phone_call_prohibited_message);
dialogFragment.show(getFragmentManager(), "phone_prohibited_dialog");
}
// Clear the digits just in case.
clearDialpad();
} else {
final Intent intent =
new CallIntentBuilder(number, CallInitiationType.Type.DIALPAD).build();
DialerUtils.startActivityWithErrorToast(getActivity(), intent);
hideAndClearDialpad(false); //隐藏拨号键盘
}
}
}
看一下 call intent 的结构。
uri 是由String number 包装得到。
public Intent build() {
Intent intent = new Intent(Intent.ACTION_CALL, uri);
// 是否视频通话
intent.putExtra(
TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
isVideoCall ? VideoProfile.STATE_BIDIRECTIONAL : VideoProfile.STATE_AUDIO_ONLY);
Bundle extras = new Bundle();
extras.putLong(Constants.EXTRA_CALL_CREATED_TIME_MILLIS, SystemClock.elapsedRealtime());
CallIntentParser.putCallSpecificAppData(extras, callSpecificAppData);
intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
if (phoneAccountHandle != null) {
intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle);
}
if (!TextUtils.isEmpty(callSubject)) {
intent.putExtra(TelecomManager.EXTRA_CALL_SUBJECT, callSubject);
}
return intent;
}
DialerUtils.java
startActivityWithErrorToast 方法主要是将键盘的触点位置传入intent.
public static void startActivityWithErrorToast(
final Context context, final Intent intent, int msgId) {
try {
if ((Intent.ACTION_CALL.equals(intent.getAction()))) {
// All dialer-initiated calls should pass the touch point to the InCallUI
// 加入用户在键盘上的触点,后续启动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);
}
// wps 网络是美国的高优先级的网络,使得紧急通话不会被无线通话网络阻塞。
if (shouldWarnForOutgoingWps(context, intent.getData().getSchemeSpecificPart())) {
LogUtil.i(
"DialUtils.startActivityWithErrorToast",
"showing outgoing WPS dialog before placing call");
AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(R.string.outgoing_wps_warning);
builder.setPositiveButton(
R.string.dialog_continue,
new OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
placeCallOrMakeToast(context, intent);
}
});
builder.setNegativeButton(android.R.string.cancel, null);
builder.create().show();
} else {
placeCallOrMakeToast(context, intent);
}
} else {
context.startActivity(intent);
}
} catch (ActivityNotFoundException e) {
Toast.makeText(context, msgId, Toast.LENGTH_SHORT).show();
}
}
private static void placeCallOrMakeToast(Context context, Intent intent) {
final boolean hasCallPermission = TelecomUtil.placeCall(context, intent);
if (!hasCallPermission) {
// TODO: Make calling activity show request permission dialog and handle
// callback results appropriately.
Toast.makeText(context, "Cannot place call without Phone permission", Toast.LENGTH_SHORT)
.show();
}
}
public static boolean placeCall(Context context, Intent intent) {
if (hasCallPhonePermission(context)) {
getTelecomManager(context).placeCall(intent.getData(), intent.getExtras());
return true;
}
return false;
}
二 /packages/services/Telecomm 层
/frameworks/base/telecomm/java/android/telecom/TelecomManager.java
public void placeCall(Uri address, Bundle extras) {
ITelecomService service = getTelecomService();
if (service != null) {
if (address == null) {
Log.w(TAG, "Cannot place call to empty address.");
}
try {
service.placeCall(address, extras == null ? new Bundle() : extras,
mContext.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecomService#placeCall", e);
}
}
}
ITelecomService 在TelecomServiceImpl.java 中实现
/packages/services/Telecomm/src/com/android/server/telecom/TelecomServiceImpl.java
public class TelecomServiceImpl {
private static final int DEFAULT_VIDEO_STATE = -1;
private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub() {
......
/**
* @see android.telecom.TelecomManager#placeCall
*/
@Override
public void placeCall(Uri handle, Bundle extras, String callingPackage) {
try {
//权限检查和包检查
......
synchronized (mLock) {
final UserHandle userHandle = Binder.getCallingUserHandle();
long token = Binder.clearCallingIdentity();
try {
final Intent intent = new Intent(Intent.ACTION_CALL, handle);
if (extras != null) {
extras.setDefusable(true);
intent.putExtras(extras);
}
mUserCallIntentProcessorFactory.create(mContext, userHandle)
.processIntent(
intent, callingPackage, isSelfManaged ||
(hasCallAppOp && hasCallPermission));
} finally {
Binder.restoreCallingIdentity(token);
}
}
} finally {
Log.endSession();
}
}
这里包含了/packages/services/Telecomm 暴露给外层的接口,比如acceptRingingCall,addNewIncomingCall,silenceRinger等方法。
/packages/services/Telecomm/src/com/android/server/telecom/components/UserCallIntentProcessor.java
processOutgoingCallIntent 将Intent 保存的data,Scheme 等信息重新取出并重新包装发送给PrimaryCallReceiver。
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()) {
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);
}
}
private void processOutgoingCallIntent(Intent intent, String callingPackageName,
boolean canCallNonEmergency) {
Uri handle = intent.getData();
String scheme = handle.getScheme();
// uriString 是电话号码
String uriString = handle.getSchemeSpecificPart();
//修正handle,判断是sip 通话还是tel 通话
if (!PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
handle = Uri.fromParts(PhoneNumberUtils.isUriNumber(uriString) ?
PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, uriString, null);
}
......
int videoState = intent.getIntExtra(
TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
VideoProfile.STATE_AUDIO_ONLY);
Log.d(this, "processOutgoingCallIntent videoState = " + videoState);
intent.putExtra(CallIntentProcessor.KEY_IS_PRIVILEGED_DIALER,
isDefaultOrSystemDialer(callingPackageName));
// Save the user handle of current user before forwarding the intent to primary user.
// 保存用户句柄mUserHandle
intent.putExtra(CallIntentProcessor.KEY_INITIATING_USER, mUserHandle);
sendBroadcastToReceiver(intent);
}
private boolean sendBroadcastToReceiver(Intent intent) {
intent.putExtra(CallIntentProcessor.KEY_IS_INCOMING_CALL, false);
intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
intent.setClass(mContext, PrimaryCallReceiver.class);
Log.d(this, "Sending broadcast as user to CallReceiver");
mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
return true;
}
/packages/services/Telecomm/src/com/android/server/telecom/components/PrimaryCallReceiver.java
public class PrimaryCallReceiver extends BroadcastReceiver implements TelecomSystem.Component {
@Override
public void onReceive(Context context, Intent intent) {
Log.startSession("PCR.oR");
synchronized (getTelecomSystem().getLock()) {
getTelecomSystem().getCallIntentProcessor().processIntent(intent);
}
Log.endSession();
}
}
/packages/services/Telecomm/src/com/android/server/telecom/CallIntentProcessor.java
CallIntentProcessor 和CallsManager是在TelecomService 启动时建立,TelecomService->TelecomSystem->CallsManager。
static void processOutgoingCallIntent(
Context context,
CallsManager callsManager,
Intent intent) {
Uri handle = intent.getData();
String scheme = handle.getScheme();
String uriString = handle.getSchemeSpecificPart();
if (!PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
handle = Uri.fromParts(PhoneNumberUtils.isUriNumber(uriString) ?
PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, uriString, null);
}
PhoneAccountHandle phoneAccountHandle = intent.getParcelableExtra(
TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
Bundle clientExtras = null;
if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) {
clientExtras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
}
if (clientExtras == null) {
clientExtras = new Bundle();
}
// Ensure call subject is passed on to the connection service.
if (intent.hasExtra(TelecomManager.EXTRA_CALL_SUBJECT)) {
String callsubject = intent.getStringExtra(TelecomManager.EXTRA_CALL_SUBJECT);
clientExtras.putString(TelecomManager.EXTRA_CALL_SUBJECT, callsubject);
}
final int videoState = intent.getIntExtra( TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
VideoProfile.STATE_AUDIO_ONLY);
clientExtras.putInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE, videoState);
......
UserHandle initiatingUser = intent.getParcelableExtra(KEY_INITIATING_USER);
// Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
//根据handle 建立新的Call 对象,但是这时没有对Call 进行处理, 而是保存到Set<Call> callsManager.mCalls中.并启动InCallUi
Call call = callsManager
.startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser,
intent);
if (call != null) {
sendNewOutgoingCallIntent(context, call, callsManager, intent);
}
}
static void sendNewOutgoingCallIntent(Context context, Call call, CallsManager callsManager,
Intent intent) {
final boolean isPrivilegedDialer = intent.getBooleanExtra(KEY_IS_PRIVILEGED_DIALER, false);
NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
context, callsManager, call, intent, callsManager.getPhoneNumberUtilsAdapter(),
isPrivilegedDialer);
final int result = broadcaster.processIntent();
final boolean success = result == DisconnectCause.NOT_DISCONNECTED;
if (!success && call != null) {
disconnectCallAndShowErrorDialog(context, call, result);
}
}
CallsManager 去建立Call 对象。 CallsManager 负责对于Call 进行向下的控制, 同时监听Call 状态的改变, 并向上发出通知。
但是这里只是生成Call 对象, 还没有向下发出拨号请求。
/packages/services/Telecomm/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
这里看到processIntent方法主要对三种类型call的一些检查,
1.普通call Intent.ACTION_CALL
普通call任何应用都可以发起,第三方应用拨号都是使用该intent
2.系统call Intent.ACTION_CALL_PRIVILEGED
系统call只有系统应用才能使用
3.紧急呼叫call Intent.ACTION_CALL_EMERGENCY
紧急呼叫call 同样只有系统应用才能使用,并且可以在无卡状态下拨.
public int processIntent() {
Log.v(this, "Processing call intent in OutgoingCallIntentBroadcaster.");
Intent intent = mIntent;
String action = intent.getAction();
final Uri handle = intent.getData();
if (handle == null) {
Log.w(this, "Empty handle obtained from the call intent.");
return DisconnectCause.INVALID_NUMBER;
}
boolean isVoicemailNumber = PhoneAccount.SCHEME_VOICEMAIL.equals(handle.getScheme());
......
String number = mPhoneNumberUtilsAdapter.getNumberFromIntent(intent, mContext);
if (TextUtils.isEmpty(number)) {
Log.w(this, "Empty number obtained from the call intent.");
return DisconnectCause.NO_PHONE_NUMBER_SUPPLIED;
}
boolean isUriNumber = mPhoneNumberUtilsAdapter.isUriNumber(number);
if (!isUriNumber) {
number = mPhoneNumberUtilsAdapter.convertKeypadLettersToDigits(number);
number = mPhoneNumberUtilsAdapter.stripSeparators(number);
}
//重新验证紧急呼叫号码,重写action
final boolean isPotentialEmergencyNumber = isPotentialEmergencyNumber(number);
Log.v(this, "isPotentialEmergencyNumber = %s", isPotentialEmergencyNumber);
rewriteCallIntentAction(intent, isPotentialEmergencyNumber);
action = intent.getAction();
// True for certain types of numbers that are not intended to be intercepted or modified
// by third parties (e.g. emergency numbers).
boolean callImmediately = false;
//验证number和action是否匹配, 不匹配则cancle, 匹配则检验是否是紧急通话
if (Intent.ACTION_CALL.equals(action)) {
if (isPotentialEmergencyNumber) {
if (!mIsDefaultOrSystemPhoneApp) {
Log.w(this, "Cannot call potential emergency number %s with CALL Intent %s "
+ "unless caller is system or default dialer.", number, intent);
launchSystemDialer(intent.getData());
return DisconnectCause.OUTGOING_CANCELED;
} else {
callImmediately = true;
}
}
} else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) {
if (!isPotentialEmergencyNumber) {
Log.w(this, "Cannot call non-potential-emergency number %s with EMERGENCY_CALL "
+ "Intent %s.", number, intent);
return DisconnectCause.OUTGOING_CANCELED;
}
callImmediately = true;
} else {
Log.w(this, "Unhandled Intent %s. Ignoring and not placing call.", intent);
return DisconnectCause.INVALID_NUMBER;
}
//紧急呼叫处理,直接调用placeOutgoingCall
if (callImmediately) {
Log.i(this, "Placing call immediately instead of waiting for "
+ " OutgoingCallBroadcastReceiver: %s", intent);
String scheme = isUriNumber ? PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL;
boolean speakerphoneOn = mIntent.getBooleanExtra(
TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false);
int videoState = mIntent.getIntExtra(
TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
VideoProfile.STATE_AUDIO_ONLY);
mCallsManager.placeOutgoingCall(mCall, Uri.fromParts(scheme, number, null), null,
speakerphoneOn, videoState);
// Don't return but instead continue and send the ACTION_NEW_OUTGOING_CALL broadcast
// so that third parties can still inspect (but not intercept) the outgoing call. When
// the broadcast finally reaches the OutgoingCallBroadcastReceiver, we'll know not to
// initiate the call again because of the presence of the EXTRA_ALREADY_CALLED extra.
}
UserHandle targetUser = mCall.getInitiatingUser();
Log.i(this, "Sending NewOutgoingCallBroadcast for %s to %s", mCall, targetUser);
broadcastIntent(intent, number, !callImmediately, targetUser);
return DisconnectCause.NOT_DISCONNECTED;
}
将Intent发送给NewOutgoingCallBroadcastIntentReceiver
private void broadcastIntent(
Intent originalCallIntent,
String number,
boolean receiverRequired,
UserHandle targetUser) {
Intent broadcastIntent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL);
if (number != null) {
broadcastIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
}
// Force receivers of this broadcast intent to run at foreground priority because we
// want to finish processing the broadcast intent as soon as possible.
broadcastIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND
| Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND);
Log.v(this, "Broadcasting intent: %s.", broadcastIntent);
checkAndCopyProviderExtras(originalCallIntent, broadcastIntent);
mContext.sendOrderedBroadcastAsUser(
broadcastIntent,
targetUser,
android.Manifest.permission.PROCESS_OUTGOING_CALLS,
AppOpsManager.OP_PROCESS_OUTGOING_CALLS,
receiverRequired ? new NewOutgoingCallBroadcastIntentReceiver() : null,
null, // scheduler
Activity.RESULT_OK, // initialCode
number, // initialData: initial value for the result data (number to be modified)
null); // initialExtras
}
public class NewOutgoingCallBroadcastIntentReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
try {
......
Uri resultHandleUri = Uri.fromParts(
mPhoneNumberUtilsAdapter.isUriNumber(resultNumber) ?
PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL,
resultNumber, null);
Uri originalUri = mIntent.getData();
......
GatewayInfo gatewayInfo = getGateWayInfoFromIntent(intent, resultHandleUri);
mCall.setNewOutgoingCallIntentBroadcastIsDone();
mCallsManager.placeOutgoingCall(mCall, resultHandleUri, gatewayInfo,
mIntent.getBooleanExtra(
TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE, false),
mIntent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
VideoProfile.STATE_AUDIO_ONLY));
}
} finally {
Trace.endSection();
Log.endSession();
}
}
}
这时mCallsManager 的placeOutgoingCall 处理call.
这里主要是对Call 的状态进行一些判断和设置,比如紧急通话等, 然后call 去建立连接。
public void placeoutgoingCall(Call call, uri handle, GatewayInfo gatewayInfo,
boolean speakerPhoneOn, int videostate) {
final Uri uriHandle = (gatewayInfo == null ) ? handle : gatewayInfo.getGatewayAddress();
call.setHandle(uriHandle);
call.setGatewayInfo(gatewayInfo);
......
if(call.getTargetPhoneAccount() != null || call.isEmergencyCall()){
if (call.isSelfManaged() && !isOutgoingCallPermitted) {
notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call);
} else if (!call.isSelfManaged() && hasSelfManagedCalls() && !call.isEmergencyCall()) {
markCallDisconnectedDueToSelfManagedCall(call);
} else {
if (call.isEmergencyCall()) {
// Disconnect all self-managed calls to make priority for emergency call.
disconnectSelfManagedCalls();
}
call.startCreateConnection(mPhoneAccountRegistrar);
}
}
......
}
/packages/services/Telecomm/src/com/android/server/telecom/Call.java
void startCreateConnection(PhoneAccountRegistrar phoneAccountRegistrar) {
if (mCreateConnectionProcessor != null) {
Log.w(this, "mCreateConnectionProcessor in startCreateConnection is not null. This is" +
" due to a race between NewOutgoingCallIntentBroadcaster and " +
"phoneAccountSelected, but is harmlessly resolved by ignoring the second " +
"invocation.");
return;
}
mCreateConnectionProcessor = new CreateConnectionProcessor(this, mRepository, this,
phoneAccountRegistrar, mContext);
mCreateConnectionProcessor.process();
}
/packages/services/Telecomm/src/com/android/server/telecom/CreateConnectionProcessor.java
private void attemptNextPhoneAccount() {
Log.v(this, "attemptNextPhoneAccount");
CallAttemptRecord attempt = null;
......
if (mCallResponse != null && attempt != null) {
Log.i(this, "Trying attempt %s", attempt);
PhoneAccountHandle phoneAccount = attempt.connectionManagerPhoneAccount;
//得到建立连接的service
mService = mRepository.getService(phoneAccount.getComponentName(),
phoneAccount.getUserHandle());
if (mService == null) {
Log.i(this, "Found no connection service for attempt %s", attempt);
attemptNextPhoneAccount();
} else {
mConnectionAttempt++;
mCall.setConnectionManagerPhoneAccount(attempt.connectionManagerPhoneAccount);
mCall.setTargetPhoneAccount(attempt.targetPhoneAccount);
mCall.setConnectionService(mService);
setTimeoutIfNeeded(mService, attempt);
// 建立连接
mService.createConnection(mCall, this);
}
} else {
Log.v(this, "attemptNextPhoneAccount, no more accounts, failing");
DisconnectCause disconnectCause = mLastErrorDisconnectCause != null ?
mLastErrorDisconnectCause : new DisconnectCause(DisconnectCause.ERROR);
notifyCallConnectionFailure(disconnectCause);
}
}
/packages/services/Telecomm/src/com/android/server/telecom/ConnectionServiceWrapper.java
如果BindService 成功,则调用onSuccess()。
onSuccess() 方法将生成ConnectionRequest 然后去调用ConnectionService 建立连接。
@VisibleForTesting
public void createConnection(final Call call, final CreateConnectionResponse response) {
Log.d(this, "createConnection(%s) via %s.", call, getComponentName());
BindCallback callback = new BindCallback() {
@Override
public void onSuccess() {
......
//初始化一个 ConnectionRequest,ConnectionRequest 保存重要参数
ConnectionRequest connectionRequest = new ConnectionRequest.Builder()
.setAccountHandle(call.getTargetPhoneAccount())
.setAddress(call.getHandle())
.setExtras(extras)
.setVideoState(call.getVideoState())
.setTelecomCallId(callId)
// For self-managed incoming calls, if there is another ongoing call Telecom
// is responsible for showing a UI to ask the user if they'd like to answer
// this new incoming call.
.setShouldShowIncomingCallUi(
!mCallsManager.shouldShowSystemIncomingCallUi(call))
.setRttPipeFromInCall(call.getInCallToCsRttPipeForCs())
.setRttPipeToInCall(call.getCsToInCallRttPipeForCs())
.build();
try {
mServiceInterface.createConnection(
call.getConnectionManagerPhoneAccount(),
callId,
connectionRequest,
call.shouldAttachToExistingConnection(),
call.isUnknown(),
Log.getExternalSession());
} catch (RemoteException e) {
Log.e(this, e, "Failure to createConnection -- %s", getComponentName());
mPendingResponses.remove(callId).handleCreateConnectionFailure(
new DisconnectCause(DisconnectCause.ERROR, e.toString()));
}
}
@Override
public void onFailure() {
Log.e(this, new Exception(), "Failure to call %s", getComponentName());
response.handleCreateConnectionFailure(new DisconnectCause(DisconnectCause.ERROR));
}
};
mBinder.bind(callback, call);
}
ConnectionRequest 保存了哪些重要参数
public final class ConnectionRequest implements Parcelable {
/**
* Builder class for {@link ConnectionRequest}
* @hide
*/
public static final class Builder {
private PhoneAccountHandle mAccountHandle;
private Uri mAddress;
private Bundle mExtras;
private int mVideoState = VideoProfile.STATE_AUDIO_ONLY;
private String mTelecomCallId;
private boolean mShouldShowIncomingCallUi = false;
private ParcelFileDescriptor mRttPipeToInCall;
private ParcelFileDescriptor mRttPipeFromInCall;
public Builder() { }
......
}
}
三 /packages/services/Telephony 层
/frameworks/base/telecomm/java/android/telecom/ConnectionService.java
private void createConnection(
final PhoneAccountHandle callManagerAccount,
final String callId,
final ConnectionRequest request,
boolean isIncoming,
boolean isUnknown) {
Log.d(this, "createConnection, callManagerAccount: %s, callId: %s, request: %s, " +
"isIncoming: %b, isUnknown: %b", callManagerAccount, callId, request,
isIncoming,
isUnknown);
// 建立 Connection
Connection connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
: isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
: onCreateOutgoingConnection(callManagerAccount, request);
connection.setTelecomCallId(callId);
//这里将向connection 设置Listener 回调, 当connection 发出通知后,一直调用到Call对象来处理
if (connection.getState() != Connection.STATE_DISCONNECTED) {
addConnection(callId, connection);
}
//回调接口,mAdapter处理从connection listener 发出的通知, 然后回调到call 处理。
mAdapter.handleCreateConnectionComplete(
callId,
request,
new ParcelableConnection(
request.getAccountHandle(),
connection.getState(),
connection.getConnectionCapabilities(),
connection.getConnectionProperties(),
connection.getSupportedAudioRoutes(),
connection.getAddress(),
connection.getAddressPresentation(),
connection.getCallerDisplayName(),
connection.getCallerDisplayNamePresentation(),
connection.getVideoProvider() == null ?
null : connection.getVideoProvider().getInterface(),
connection.getVideoState(),
connection.isRingbackRequested(),
connection.getAudioModeIsVoip(),
connection.getConnectTimeMillis(),
connection.getStatusHints(),
connection.getDisconnectCause(),
createIdList(connection.getConferenceables()),
connection.getExtras()));
}
/packages/services/Telephony/src/com/android/services/telephony/TelephonyConnectionService.java
TelephonyConnectionService 开始主要是状态和参数检验,
@Override
public Connection onCreateOutgoingConnection(
PhoneAccountHandle connectionManagerPhoneAccount,
final ConnectionRequest request) {
Log.i(this, "onCreateOutgoingConnection, request: " + request);
Uri handle = request.getAddress();
if (handle == null) {
Log.d(this, "onCreateOutgoingConnection, handle is null");
return Connection.createFailedConnection(
DisconnectCauseUtil.toTelecomDisconnectCause(
android.telephony.DisconnectCause.NO_PHONE_NUMBER_SUPPLIED,
"No phone number supplied"));
}
String scheme = handle.getScheme();
String number;
//对scheme 和number进行校验,不符合条件返回failconnection
if (PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
// TODO: We don't check for SecurityException here (requires
// CALL_PRIVILEGED permission).
final Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
if (phone == null) {
Log.d(this, "onCreateOutgoingConnection, phone is null");
return Connection.createFailedConnection(
DisconnectCauseUtil.toTelecomDisconnectCause(
android.telephony.DisconnectCause.OUT_OF_SERVICE,
"Phone is null"));
}
number = phone.getVoiceMailNumber();
if (TextUtils.isEmpty(number)) {
Log.d(this, "onCreateOutgoingConnection, no voicemail number set.");
return Connection.createFailedConnection(
DisconnectCauseUtil.toTelecomDisconnectCause(
android.telephony.DisconnectCause.VOICEMAIL_NUMBER_MISSING,
"Voicemail scheme provided but no voicemail number set."));
}
// Convert voicemail: to tel:
// 将voicemail 转化为tel.
handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
} else {
if (!PhoneAccount.SCHEME_TEL.equals(scheme)) {
Log.d(this, "onCreateOutgoingConnection, Handle %s is not type tel", scheme);
return Connection.createFailedConnection(
DisconnectCauseUtil.toTelecomDisconnectCause(
android.telephony.DisconnectCause.INVALID_NUMBER,
"Handle scheme is not type tel"));
}
number = handle.getSchemeSpecificPart();
if (TextUtils.isEmpty(number)) {
Log.d(this, "onCreateOutgoingConnection, unable to parse number");
return Connection.createFailedConnection(
DisconnectCauseUtil.toTelecomDisconnectCause(
android.telephony.DisconnectCause.INVALID_NUMBER,
"Unable to parse number"));
}
final Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
......
// Convert into emergency number if necessary
// This is required in some regions (e.g. *).
if (!PhoneNumberUtils.isLocalEmergencyNumber(this, number) &&
PhoneNumberUtils.isConvertToEmergencyNumberEnabled()) {
final Phone phone = getPhoneForAccount(request.getAccountHandle(), false);
// We only do the conversion if the phone is not in service. The un-converted
// emergency numbers will go to the correct destination when the phone is in-service,
// so they will only need the special emergency call setup when the phone is out of
// service.
if (phone == null || phone.getServiceState().getState()
!= ServiceState.STATE_IN_SERVICE) {
String convertedNumber = PhoneNumberUtils.convertToEmergencyNumber(number);
if (!TextUtils.equals(convertedNumber, number)) {
Log.i(this, "onCreateOutgoingConnection, converted to emergency number");
number = convertedNumber;
handle = Uri.fromParts(PhoneAccount.SCHEME_TEL, number, null);
}
}
}
final String numberToDial = number;
final boolean isEmergencyNumber =
PhoneNumberUtils.isLocalEmergencyNumber(this, numberToDial);
final boolean isAirplaneModeOn = Settings.Global.getInt(getContentResolver(),
Settings.Global.AIRPLANE_MODE_ON, 0) > 0;
//此处是在rodio 关闭情况下的紧急通话。尝试去打开radio,在拨打紧急通话。
......
} else {
// Get the right phone object from the account data passed in.
final Phone phone = getPhoneForAccount(request.getAccountHandle(), isEmergencyNumber);
// 1 得到Connection
Connection resultConnection = getTelephonyConnection(request, numberToDial,
isEmergencyNumber, handle, phone);
// If there was a failure, the resulting connection will not be a TelephonyConnection,
// so don't place the call!
if(resultConnection instanceof TelephonyConnection) {
// 2 处理电话连接
placeOutgoingConnection((TelephonyConnection) resultConnection, phone, request);
}
return resultConnection;
}
}
placeOutgoingConnection 方法很简单。 调用Phone.dial(), 返回一个telephony.Connection 。
然后将telephony.Connection 传递给TelephonyConnection。
private void placeOutgoingConnection(
TelephonyConnection connection, Phone phone, int videoState, Bundle extras) {
String number = connection.getAddress().getSchemeSpecificPart();
com.android.internal.telephony.Connection originalConnection = null;
try {
if (phone != null) {
//1. 调用phone处理电话
originalConnection = phone.dial(number, null, videoState, extras);
}
} catch (CallStateException e) {
Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e);
int cause = android.telephony.DisconnectCause.OUTGOING_FAILURE;
if (e.getError() == CallStateException.ERROR_DISCONNECTED) {
cause = android.telephony.DisconnectCause.OUT_OF_SERVICE;
} else if (e.getError() == CallStateException.ERROR_POWER_OFF) {
cause = android.telephony.DisconnectCause.POWER_OFF;
}
connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
cause, 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
//2. 如果是gsmphone, phone返回connection 为null,则是MMI code。
if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM) {
Log.d(this, "dialed MMI code");
int subId = phone.getSubId();
Log.d(this, "subId: "+subId);
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);
if (SubscriptionManager.isValidSubscriptionId(subId)) {
intent.putExtra(PhoneConstants.SUBSCRIPTION_KEY, subId);
}
startActivity(intent);
}
Log.d(this, "placeOutgoingConnection, phone.dial returned null");
connection.setDisconnected(DisconnectCauseUtil.toTelecomDisconnectCause(
telephonyDisconnectCause, "Connection is null"));
} else {
connection.setOriginalConnection(originalConnection);
}
}
connection.setOriginalConnection 向phone 注册call状态改变的通知消息 和一些重要参数。
void setOriginalConnection(com.android.internal.telephony.Connection originalConnection) {
Log.v(this, "new TelephonyConnection, originalConnection: " + originalConnection);
clearOriginalConnection();
mOriginalConnectionExtras.clear();
mOriginalConnection = originalConnection;
mOriginalConnection.setTelecomCallId(getTelecomCallId());
//向phone注册状态改变的通知消息。
getPhone().registerForPreciseCallStateChanged(
mHandler, MSG_PRECISE_CALL_STATE_CHANGED, null);
getPhone().registerForHandoverStateChanged(
mHandler, MSG_HANDOVER_STATE_CHANGED, null);
getPhone().registerForRingbackTone(mHandler, MSG_RINGBACK_TONE, null);
getPhone().registerForDisconnect(mHandler, MSG_DISCONNECT, null);
getPhone().registerForSuppServiceNotification(mHandler, MSG_SUPP_SERVICE_NOTIFY, null);
getPhone().registerForOnHoldTone(mHandler, MSG_ON_HOLD_TONE, null);
mOriginalConnection.addPostDialListener(mPostDialListener);
mOriginalConnection.addListener(mOriginalConnectionListener);
// 设置一些重要的参数
// Set video state and capabilities
setVideoState(mOriginalConnection.getVideoState());
setOriginalConnectionCapabilities(mOriginalConnection.getConnectionCapabilities());
setWifi(mOriginalConnection.isWifi());
setVideoProvider(mOriginalConnection.getVideoProvider());
setAudioQuality(mOriginalConnection.getAudioQuality());
setTechnologyTypeExtra();
// Post update of extras to the handler; extras are updated via the handler to ensure thread
// safety. The Extras Bundle is cloned in case the original extras are modified while they
// are being added to mOriginalConnectionExtras in updateExtras.
Bundle connExtras = mOriginalConnection.getConnectionExtras();
mHandler.obtainMessage(MSG_CONNECTION_EXTRAS_CHANGED, connExtras == null ? null :
new Bundle(connExtras)).sendToTarget();
if (PhoneNumberUtils.isEmergencyNumber(mOriginalConnection.getAddress())) {
mTreatAsEmergencyCall = true;
}
if (isImsConnection()) {
mWasImsConnection = true;
}
mIsMultiParty = mOriginalConnection.isMultiparty();
// updateState can set mOriginalConnection to null if its state is DISCONNECTED, so this
// should be executed *after* the above setters have run.
updateState();
if (mOriginalConnection == null) {
Log.w(this, "original Connection was nulled out as part of setOriginalConnection. " +
originalConnection);
}
fireOnOriginalConnectionConfigured();
}
四 frameworks/opt/telephony 层
@Override
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!");
}
......
// IMS call
if ((imsUseEnabled && (!isUt || useImsForUt)) || useImsForEmergency) {
try {
if (DBG) logd("Trying IMS PS call");
return imsPhone.dial(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;
}
}
}
......
if (isPhoneTypeGsm()) {
return dialInternal(dialString, null, VideoProfile.STATE_AUDIO_ONLY, intentExtras);
} else {
return dialInternal(dialString, null, videoState, intentExtras);
}
}
protected Connection dialInternal(String dialString, UUSInfo uusInfo, int videoState,
Bundle intentExtras, ResultReceiver wrappedCallback)
throws CallStateException {
// Need to make sure dialString gets parsed properly
String newDialString = PhoneNumberUtils.stripSeparators(dialString);
if (isPhoneTypeGsm()) {
// Only look at the Network portion for mmi
String networkPortion = PhoneNumberUtils.extractNetworkPortionAlt(newDialString);
GsmMmiCode mmi = GsmMmiCode.newFromDialString(networkPortion, this,
mUiccApplication.get(), wrappedCallback);
if (DBG) logd("dialInternal: dialing w/ mmi '" + mmi + "'...");
if (mmi == null) {
return mCT.dial(newDialString, uusInfo, intentExtras);
} else if (mmi.isTemporaryModeCLIR()) {
return mCT.dial(mmi.mDialingNumber, mmi.getCLIRMode(), uusInfo, intentExtras);
} else {
mPendingMMIs.add(mmi);
mMmiRegistrants.notifyRegistrants(new AsyncResult(null, mmi, null));
mmi.processCode();
return null;
}
} else {
return mCT.dial(newDialString);
}
}
/frameworks/opt/telephony/src/java/com/android/internal/telephony/GsmCdmaCallTracker.java
//GSM
/**
* clirMode is one of the CLIR_ constants
*/
public synchronized Connection dial(String dialString, int clirMode, UUSInfo uusInfo,
Bundle intentExtras)
throws CallStateException {
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
//如果当前有一个call处于前台,我们需要将它hold住,并通过一个500毫秒的延时来执行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
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();
}
boolean isEmergencyCall = PhoneNumberUtils.isLocalEmergencyNumber(mPhone.getContext(),
dialString);
mPendingMO = new GsmCdmaConnection(mPhone, checkForTestEmergencyNumber(dialString),
this, mForegroundCall, isEmergencyCall);
mHangupPendingMO = false;
mMetrics.writeRilDial(mPhone.getPhoneId(), mPendingMO, clirMode, uusInfo);
if ( mPendingMO.getAddress() == null || mPendingMO.getAddress().length() == 0
|| mPendingMO.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0) {
// Phone number is invalid
mPendingMO.mCause = DisconnectCause.INVALID_NUMBER;
// handlePollCalls() will notice this call not present
// and will mark it as dropped.
pollCallsWhenSafe();
} else {
// Always unmute when initiating a new call
setMute(false);
// 1. RIL 的 dial
mCi.dial(mPendingMO.getAddress(), clirMode, uusInfo, obtainCompleteMessage());
}
if (mNumberConverted) {
mPendingMO.setConverted(origNumber);
mNumberConverted = false;
}
updatePhoneState();
mPhone.notifyPreciseCallStateChanged();
return mPendingMO;
}