Telephony上层的代码主要由六部分组成:
frameworks/opt/telephony/ telephony-common.jar
frameworks/base/telecomm/ framework-telecomm framework.jar
frameworks/base/telephony/ framework-telephony framework.jar
packages/services/Telephony/ TeleService.apk
packages/services/Telecomm/ Telecom.apk
packages/apps/Dialer/ Dialer.apk
本文中代码来源于MTK Android 14源码。
MO 显示 InCall UI 流程
显示 InCall UI 流程主要分为 3 部分:
1.通过CallIntentProcessor处理Call Intent ;
2.InCallController 绑定 InCallService;
3.InCallService 通过 InCallPresenter 拉起UI;
1.拨出电话从 DialpadFragment 的拨号按钮按下开始,直到调用到 TelecomManager 的 placeCall()方法进入到真正的流程入口
packages/apps/Dialer/java/com/android/dialer/dialpadview/DialpadFragment.java
public void onClick(View view) {
int resId = view.getId();
if (resId == R.id.dialpad_floating_action_button) {
view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
handleDialButtonPressed();
} else if (resId == R.id.deleteButton) {
...
}
}
private void handleDialButtonPressed() {
if (isDigitsEmpty()) { // No number entered.
// No real call made, so treat it as a click
PerformanceReport.recordClick(UiAction.Type.PRESS_CALL_BUTTON_WITHOUT_CALLING);
handleDialButtonClickWithEmptyDigits();
} else {
final String number = digits.getText().toString();
...
} else{
PreCall.start(getContext(), new CallIntentBuilder(number, CallInitiationType.Type.DIALPAD));
hideAndClearDialpad();
}
}
packages/apps/Dialer/java/com/android/dialer/precall/PreCall.java
static void start(Context context, CallIntentBuilder builder) {
DialerUtils.startActivityWithErrorToast(context, getIntent(context, builder));
}
packages/apps/Dialer/java/com/android/dialer/util/DialerUtils.java
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
Point touchPoint = TouchPointManager.getInstance().getPoint();
...
if (shouldWarnForOutgoingWps(context, intent.getData().getSchemeSpecificPart())) {
...
} 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();
}
}
2.TelecomManager 的服务实现 TelecomServiceImpl 将检查发起拨号的应用是否有拨号的权限,如果有则开始处理 拨号的 Intent
packages/apps/Dialer/java/com/android/dialer/telecom/TelecomUtil.java
public static boolean placeCall(Context context, Intent intent) {
if (hasCallPhonePermission(context)) {
//第一次跨进程调用
getTelecomManager(context).placeCall(intent.getData(), intent.getExtras());
return true;
}
return false;
}
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(), mContext.getAttributionTag());
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecomService#placeCall", e);
}
}
}
3.拨号 Intent 由 UserCallIntentProcessor 处理,最终调用到 CallIntentProcessor 进行进一步处理
packages/services/Telecomm/src/com/android/server/telecom/TelecomServiceImpl.java
public void placeCall(Uri handle, Bundle extras, String callingPackage,
String callingFeatureId) {
try {
Log.startSession("TSI.pC");
enforceCallingPackage(callingPackage);
...
synchronized (mLock) {
final UserHandle userHandle = Binder.getCallingUserHandle();
long token = Binder.clearCallingIdentity();
try {
final Intent intent = new Intent(hasCallPrivilegedPermission ?
Intent.ACTION_CALL_PRIVILEGED : Intent.ACTION_CALL, handle);
if (extras != null) {
extras.setDefusable(true);
intent.putExtras(extras);
}
mUserCallIntentProcessorFactory.create(mContext, userHandle)
.processIntent(
intent, callingPackage, isSelfManaged ||
(hasCallAppOp && hasCallPermission),
true /* isLocalInvocation */);
} finally {
Binder.restoreCallingIdentity(token);
}
}
} finally {
Log.endSession();
}
}
packages/services/Telecomm/src/com/android/server/telecom/components/UserCallIntentProcessor.java
public void processIntent(Intent intent, String callingPackageName,
boolean canCallNonEmergency, boolean isLocalInvocation) {
// 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,
isLocalInvocation);
}
}
private void processOutgoingCallIntent(Intent intent, String callingPackageName,
boolean canCallNonEmergency, boolean isLocalInvocation) {
Uri handle = intent.getData();
String scheme = handle.getScheme();
String uriString = handle.getSchemeSpecificPart();
...
if (!canCallNonEmergency && !TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
showErrorDialogForRestrictedOutgoingCall(mContext,
R.string.outgoing_call_not_allowed_no_permission);
Log.w(this, "Rejecting non-emergency phone call because "
+ android.Manifest.permission.CALL_PHONE + " permission is not granted.");
return;
}
int videoState = intent.getIntExtra(
TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
VideoProfile.STATE_AUDIO_ONLY);
Log.d(this, "processOutgoingCallIntent videoState = " + videoState);
// Save the user handle of current user before forwarding the intent to primary user.
intent.putExtra(CallIntentProcessor.KEY_INITIATING_USER, mUserHandle);
sendIntentToDestination(intent, isLocalInvocation, callingPackageName);
}
private boolean sendIntentToDestination(Intent intent, boolean isLocalInvocation,
String callingPackage) {
intent.putExtra(CallIntentProcessor.KEY_IS_INCOMING_CALL, false);
intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
if (isLocalInvocation) {
// We are invoking this from TelecomServiceImpl, so TelecomSystem is available. Don't
// bother trampolining the intent, just sent it directly to the call intent processor.
// TODO: We should not be using an intent here; this whole flows needs cleanup.
Log.i(this, "sendIntentToDestination: send intent to Telecom directly.");
synchronized (TelecomSystem.getInstance().getLock()) {
TelecomSystem.getInstance().getCallIntentProcessor().processIntent(intent,
callingPackage);
}
} else {
// We're calling from the UserCallActivity, so the TelecomSystem is not in the same
// process; we need to trampoline to TelecomSystem in the system server process.
Log.i(this, "sendIntentToDestination: trampoline to Telecom.");
TelecomManager tm = (TelecomManager) mContext.getSystemService(Context.TELECOM_SERVICE);
tm.handleCallIntent(intent, callingPackage);
}
return true;
}
4.CallIntentProcessor 有两个流程分支,其中拉起 InCall UI 是直接进入 CallsManager 中调用 addCall 方法,并唤醒监听该消息的 listener
packages/services/Telecomm/src/com/android/server/telecom/CallIntentProcessor.java
public void processIntent(Intent intent, String callingPackage) {
final boolean isUnknownCall = intent.getBooleanExtra(KEY_IS_UNKNOWN_CALL, false);
Log.i(this, "onReceive - isUnknownCall: %s", isUnknownCall);
Trace.beginSection("processNewCallCallIntent");
if (isUnknownCall) {
processUnknownCallIntent(mCallsManager, intent);
} else {
processOutgoingCallIntent(mContext, mCallsManager, intent, callingPackage,
mDefaultDialerCache);
}
Trace.endSection();
}
static void processOutgoingCallIntent(
Context context,
CallsManager callsManager,
Intent intent,
String callingPackage,
DefaultDialerCache defaultDialerCache) {
Uri handle = intent.getData();
String scheme = handle.getScheme();
String uriString = handle.getSchemeSpecificPart();
...
boolean isPrivilegedDialer = defaultDialerCache.isDefaultOrSystemDialer(callingPackage,
initiatingUser.getIdentifier());
NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
context, callsManager, intent, callsManager.getPhoneNumberUtilsAdapter(),
isPrivilegedDialer, defaultDialerCache);
// If the broadcaster comes back with an immediate error, disconnect and show a dialog.
NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster.evaluateCall();
if (disposition.disconnectCause != DisconnectCause.NOT_DISCONNECTED) {
showErrorDialog(context, disposition.disconnectCause);
return;
}
// Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
CompletableFuture<Call> callFuture = callsManager
.startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser,
intent, callingPackage);
final Session logSubsession = Log.createSubsession();
callFuture.thenAccept((call) -> {
if (call != null) {
Log.continueSession(logSubsession, "CIP.sNOCI");
try {
broadcaster.processCall(call, disposition);
} finally {
Log.endSession();
}
}
});
}
packages/services/Telecomm/src/com/android/server/telecom/CallsManager.java
private CompletableFuture<Call> startOutgoingCall(List<Uri> participants,
PhoneAccountHandle requestedAccountHandle,
Bundle extras, UserHandle initiatingUser, Intent originalIntent,
String callingPackage, boolean isConference) {
boolean isReusedCall;
Uri handle = isConference ? Uri.parse("tel:conf-factory") : participants.get(0);
Call call = reuseOutgoingCall(handle);
PhoneAccount account =
mPhoneAccountRegistrar.getPhoneAccount(requestedAccountHandle, initiatingUser);
Bundle phoneAccountExtra = account != null ? account.getExtras() : null;
boolean isSelfManaged = account != null && account.isSelfManaged();
...
mLatestPostSelectionProcessingFuture = dialerSelectPhoneAccountFuture
.thenComposeAsync(args -> {
if (args == null) {
return CompletableFuture.completedFuture(null);
}
Log.i(CallsManager.this, "post acct selection stage");
Call callToUse = args.first;
PhoneAccountHandle phoneAccountHandle = args.second;
PhoneAccount accountToUse = mPhoneAccountRegistrar
.getPhoneAccount(phoneAccountHandle, initiatingUser);
callToUse.setTargetPhoneAccount(phoneAccountHandle);
if (accountToUse != null && accountToUse.getExtras() != null) {
if (accountToUse.getExtras()
.getBoolean(PhoneAccount.EXTRA_ALWAYS_USE_VOIP_AUDIO_MODE)) {
Log.d(this, "startOutgoingCall: defaulting to voip mode for call %s",
callToUse.getId());
callToUse.setIsVoipAudioMode(true);
}
}
callToUse.setState(
CallState.CONNECTING,
phoneAccountHandle == null ? "no-handle"
: phoneAccountHandle.toString());
boolean isVoicemail = isVoicemail(callToUse.getHandle(), accountToUse);
boolean isRttSettingOn = isRttSettingOn(phoneAccountHandle);
if (!isVoicemail && (isRttSettingOn || (extras != null
&& extras.getBoolean(TelecomManager.EXTRA_START_CALL_WITH_RTT,
false)))) {
Log.d(this, "Outgoing call requesting RTT, rtt setting is %b",
isRttSettingOn);
if (callToUse.isEmergencyCall() || (accountToUse != null
&& accountToUse.hasCapabilities(PhoneAccount.CAPABILITY_RTT))) {
// If the call requested RTT and it's an emergency call, ignore the
// capability and hope that the modem will deal with it somehow.
callToUse.createRttStreams();
}
// Even if the phone account doesn't support RTT yet,
// the connection manager might change that. Set this to check it later.
callToUse.setRequestedToStartWithRtt();
}
setIntentExtrasAndStartTime(callToUse, extras);
setCallSourceToAnalytics(callToUse, originalIntent);
if (isPotentialMMICode(handle) && !isSelfManaged) {
// Do not add the call if it is a potential MMI code.
callToUse.addListener(this);
} else if (!mCalls.contains(callToUse)) {
// We check if mCalls already contains the call because we could
// potentially be reusing
// a call which was previously added (See {@link #reuseOutgoingCall}).
addCall(callToUse);
}
return CompletableFuture.completedFuture(callToUse);
}, new LoggedHandlerExecutor(outgoingCallHandler, "CM.pASP", mLock));
return mLatestPostSelectionProcessingFuture;
}
public void addCall(Call call) {
Trace.beginSection("addCall");
Log.i(this, "addCall(%s)", call);
call.addListener(this);
mCalls.add(call);
// Specifies the time telecom finished routing the call. This is used by the dialer for
// analytics.
Bundle extras = call.getIntentExtras();
extras.putLong(TelecomManager.EXTRA_CALL_TELECOM_ROUTING_END_TIME_MILLIS,
SystemClock.elapsedRealtime());
updateCanAddCall();
updateHasActiveRttCall();
updateExternalCallCanPullSupport();
// 快速设置call对象为前端通话,即第一通电话
for (CallsManagerListener listener : mListeners) {
if (LogUtils.SYSTRACE_DEBUG) {
Trace.beginSection(listener.getClass().toString() + " addCall");
}
//通知InCallController调用onCallAdded
listener.onCallAdded(call);
if (LogUtils.SYSTRACE_DEBUG) {
Trace.endSection();
}
}
Trace.endSection();
}
5.InCallController 监听到 onCallAdded()消息,framework_telecom属于framework,需要绑定InCallService才能元撑调用,开始绑定 InCallService,最终流程进入到InCallServiceImpl 子类实现中
packages/services/Telecomm/src/com/android/server/telecom/InCallController.java
public void onCallAdded(Call call) {
if (!isBoundAndConnectedToServices()) { //判断是否已绑定
Log.i(this, "onCallAdded: %s; not bound or connected.", call);
// We are not bound, or we're not connected.
bindToServices(call);
} else {
// We are bound, and we are connected.
adjustServiceBindingsForEmergency();
// This is in case an emergency call is added while there is an existing call.
mEmergencyCallHelper.maybeGrantTemporaryLocationPermission(call,
mCallsManager.getCurrentUserHandle());
Log.i(this, "onCallAdded: %s", call);
// Track the call if we don't already know about it.
addCall(call);
Log.i(this, "mInCallServiceConnection isConnected=%b",
mInCallServiceConnection.isConnected());
List<ComponentName> componentsUpdated = new ArrayList<>();
for (Map.Entry<InCallServiceInfo, IInCallService> entry : mInCallServices.entrySet()) {
InCallServiceInfo info = entry.getKey();
if (call.isExternalCall() && !info.isExternalCallsSupported()) {
continue;
}
if (call.isSelfManaged() && (!call.visibleToInCallService()
|| !info.isSelfManagedCallsSupported())) {
continue;
}
// Only send the RTT call if it's a UI in-call service
boolean includeRttCall = false;
if (mInCallServiceConnection != null) {
includeRttCall = info.equals(mInCallServiceConnection.getInfo());
}
componentsUpdated.add(info.getComponentName());
IInCallService inCallService = entry.getValue();
ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
info.isExternalCallsSupported(), includeRttCall,
info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI ||
info.getType() == IN_CALL_SERVICE_TYPE_NON_UI);
try {
inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall));
updateCallTracking(call, info, true /* isAdd */);
} catch (RemoteException ignored) {
}
}
Log.i(this, "Call added to components: %s", componentsUpdated);
}
}
public void bindToServices(Call call) {
if (mInCallServiceConnection == null) {
InCallServiceConnection dialerInCall = null;
InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent();
Log.i(this, "defaultDialer: " + defaultDialerComponentInfo);
if (defaultDialerComponentInfo != null &&
!defaultDialerComponentInfo.getComponentName().equals(
mDefaultDialerCache.getSystemDialerComponent())) {
dialerInCall = new InCallServiceBindingConnection(defaultDialerComponentInfo);
}
Log.i(this, "defaultDialer: " + dialerInCall);
InCallServiceInfo systemInCallInfo = getInCallServiceComponent(
mDefaultDialerCache.getSystemDialerComponent(), IN_CALL_SERVICE_TYPE_SYSTEM_UI);
EmergencyInCallServiceConnection systemInCall =
new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall);
systemInCall.setHasEmergency(mCallsManager.isInEmergencyCall());
InCallServiceConnection carModeInCall = null;
InCallServiceInfo carModeComponentInfo = getCurrentCarModeComponent();
if (carModeComponentInfo != null &&
!carModeComponentInfo.getComponentName().equals(
mDefaultDialerCache.getSystemDialerComponent())) {
carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo);
}
mInCallServiceConnection =
new CarSwappingInCallServiceConnection(systemInCall, carModeInCall);
}
mInCallServiceConnection.chooseInitialInCallService(shouldUseCarModeUI());
// 在这里开始绑定InCallService.
if (mInCallServiceConnection.connect(call) ==
InCallServiceConnection.CONNECTION_SUCCEEDED || call.isSelfManaged()) {
// Only connect to the non-ui InCallServices if we actually connected to the main UI
// one, or if the call is self-managed (in which case we'd still want to keep Wear, BT,
// etc. informed.
connectToNonUiInCallServices(call);
mBindingFuture = new CompletableFuture<Boolean>().completeOnTimeout(false,
mTimeoutsAdapter.getCallRemoveUnbindInCallServicesDelay(
mContext.getContentResolver()),
TimeUnit.MILLISECONDS);
} else {
Log.i(this, "bindToServices: current UI doesn't support call; not binding.");
}
IntentFilter packageChangedFilter = new IntentFilter(Intent.ACTION_PACKAGE_CHANGED);
packageChangedFilter.addDataScheme("package");
mContext.registerReceiver(mPackageChangedReceiver, packageChangedFilter);
}
private void connectToNonUiInCallServices(Call call) {
if (mNonUIInCallServiceConnections == null) {
updateNonUiInCallServices();
}
mNonUIInCallServiceConnections.connect(call);
}
private class NonUIInCallServiceConnectionCollection extends InCallServiceConnection {
@Override
public int connect(Call call) {
for (InCallServiceBindingConnection subConnection : mSubConnections) {
subConnection.connect(call);
}
return CONNECTION_SUCCEEDED;
}
}
mNonUIInCallServiceConnections是NonUIInCallServiceConnectionCollection实例。实际调用InCallServiceBindingConnection的connect()方法。InCallServiceBindingConnection是的内部类。
private class InCallServiceBindingConnection extends InCallServiceConnection {
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.startSession("ICSBC.oSC", Log.getPackageAbbreviation(name));
synchronized (mLock) {
try {
Log.d(this, "onServiceConnected: %s %b %b", name, mIsBound, mIsConnected);
mIsBound = true;
if (mIsConnected) {
// Only proceed if we are supposed to be connected.
onConnected(service);
}
} finally {
Log.endSession();
}
}
}
}
public int connect(Call call) {
if (mIsConnected) {
Log.addEvent(call, LogUtils.Events.INFO, "Already connected, ignoring request: "
+ mInCallServiceInfo);
if (call != null) {
// Track the call if we don't already know about it.
addCall(call);
// Notify this new added call
sendCallToService(call, mInCallServiceInfo,
mInCallServices.get(mInCallServiceInfo));
}
return CONNECTION_SUCCEEDED;
}
if (call != null && call.isSelfManaged() &&
(!mInCallServiceInfo.isSelfManagedCallsSupported()
|| !call.visibleToInCallService())) {
Log.i(this, "Skipping binding to %s - doesn't support self-mgd calls",
mInCallServiceInfo);
mIsConnected = false;
return CONNECTION_NOT_SUPPORTED;
}
Intent intent = new Intent(InCallService.SERVICE_INTERFACE);
intent.setComponent(mInCallServiceInfo.getComponentName());
if (call != null && !call.isIncoming() && !call.isExternalCall()) {
intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS,
call.getIntentExtras());
intent.putExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE,
call.getTargetPhoneAccount());
}
Log.i(this, "Attempting to bind to InCall %s, with %s", mInCallServiceInfo, intent);
mIsConnected = true;
mInCallServiceInfo.setBindingStartTime(mClockProxy.elapsedRealtime());
if (!mContext.bindServiceAsUser(intent, mServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
| Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
UserHandle.CURRENT)) {
Log.w(this, "Failed to connect.");
mIsConnected = false;
}
if (mIsConnected && call != null) {
mCall = call;
}
Log.i(this, "mCall: %s, mIsConnected: %s", mCall, mIsConnected);
return mIsConnected ? CONNECTION_SUCCEEDED : CONNECTION_FAILED;
}
}
ServiceConnection绑定成功以后调用InCallServiceBindingConnection.mServiceConnection .onServiceConnected(),onServiceConnected()方法内调用了InCallServiceBindingConnection.onConnected()。
packages/services/Telecomm/src/com/android/server/telecom/InCallController.java
private class InCallServiceBindingConnection extends InCallServiceConnection {
protected void onConnected(IBinder service) {
boolean shouldRemainConnected =
InCallController.this.onConnected(mInCallServiceInfo, service);
if (!shouldRemainConnected) {
// Sometimes we can opt to disconnect for certain reasons, like if the
// InCallService rejected our initialization step, or the calls went away
// in the time it took us to bind to the InCallService. In such cases, we go
// ahead and disconnect ourselves.
disconnect();
}
}
}
private boolean onConnected(InCallServiceInfo info, IBinder service) {
Log.i(this, "onConnected to %s", info.getComponentName());
if (info.getType() == IN_CALL_SERVICE_TYPE_CAR_MODE_UI
|| info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI
|| info.getType() == IN_CALL_SERVICE_TYPE_DEFAULT_DIALER_UI) {
trackCallingUserInterfaceStarted(info);
}
IInCallService inCallService = IInCallService.Stub.asInterface(service);
mInCallServices.put(info, inCallService);
try {
inCallService.setInCallAdapter(
new InCallAdapter(
mCallsManager,
mCallIdMapper,
mLock,
info.getComponentName().getPackageName()));
} catch (RemoteException e) {
Log.e(this, e, "Failed to set the in-call adapter.");
Trace.endSection();
return false;
}
// Upon successful connection, send the state of the world to the service.
List<Call> calls = orderCallsWithChildrenFirst(mCallsManager.getCalls());
Log.i(this, "Adding %s calls to InCallService after onConnected: %s, including external " +
"calls", calls.size(), info.getComponentName());
int numCallsSent = 0;
for (Call call : calls) {
//
numCallsSent += sendCallToService(call, info, inCallService);
}
try {
inCallService.onCallAudioStateChanged(mCallsManager.getAudioState());
inCallService.onCanAddCallChanged(mCallsManager.canAddCall());
} catch (RemoteException ignored) {
}
// Don't complete the binding future for non-ui incalls
if (info.getType() != IN_CALL_SERVICE_TYPE_NON_UI && !mBindingFuture.isDone()) {
mBindingFuture.complete(true);
}
Log.i(this, "%s calls sent to InCallService.", numCallsSent);
return true;
}
private int sendCallToService(Call call, InCallServiceInfo info,
IInCallService inCallService) {
try {
if ((call.isSelfManaged() && (!info.isSelfManagedCallsSupported()
|| !call.visibleToInCallService())) ||
(call.isExternalCall() && !info.isExternalCallsSupported())) {
return 0;
}
// Only send the RTT call if it's a UI in-call service
boolean includeRttCall = false;
if (mInCallServiceConnection != null) {
includeRttCall = info.equals(mInCallServiceConnection.getInfo());
}
// Track the call if we don't already know about it.
addCall(call);
ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(
call,
true /* includeVideoProvider */,
mCallsManager.getPhoneAccountRegistrar(),
info.isExternalCallsSupported(),
includeRttCall,
info.getType() == IN_CALL_SERVICE_TYPE_SYSTEM_UI ||
info.getType() == IN_CALL_SERVICE_TYPE_NON_UI);
//
inCallService.addCall(sanitizeParcelableCallForService(info, parcelableCall));
updateCallTracking(call, info, true /* isAdd */);
return 1;
} catch (RemoteException ignored) {
}
return 0;
}
frameworks/base/telecomm/java/android/telecom/InCallService.java
private final class InCallServiceBinder extends IInCallService.Stub {
@Override
public void addCall(ParcelableCall call) {
mHandler.obtainMessage(MSG_ADD_CALL, call).sendToTarget();
}
}
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
if (mPhone == null && msg.what != MSG_SET_IN_CALL_ADAPTER) {
return;
}
switch (msg.what) {
case MSG_ADD_CALL:
mPhone.internalAddCall((ParcelableCall) msg.obj);
break;
}
}
}
private Phone.Listener mPhoneListener = new Phone.Listener() {
/**
* ${inheritDoc}
*/
@Override
public void onCallAdded(Phone phone, Call call) {
InCallService.this.onCallAdded(call);
}
}
frameworks/base/telecomm/java/android/telecom/Phone.java
final void internalAddCall(ParcelableCall parcelableCall) {
if (mTargetSdkVersion < SDK_VERSION_R
&& parcelableCall.getState() == Call.STATE_AUDIO_PROCESSING) {
Log.i(this, "Skipping adding audio processing call for sdk compatibility");
return;
}
Call call = mCallByTelecomCallId.get(parcelableCall.getId());
if (call == null) {
call = new Call(this, parcelableCall.getId(), mInCallAdapter,
parcelableCall.getState(), mCallingPackage, mTargetSdkVersion);
mCallByTelecomCallId.put(parcelableCall.getId(), call);
mCalls.add(call);
checkCallTree(parcelableCall);
call.internalUpdate(parcelableCall, mCallByTelecomCallId);
fireCallAdded(call);
} else {
Log.w(this, "Call %s added, but it was already present", call.internalGetCallId());
checkCallTree(parcelableCall);
call.internalUpdate(parcelableCall, mCallByTelecomCallId);
}
}
private void fireCallAdded(Call call) {
for (Listener listener : mListeners) {
listener.onCallAdded(this, call); //回调匿名
}
}
6.InCallServiceImpl 调用 InCallPresenter 的 onCallAdded()方法
packages/apps/Dialer/java/com/android/incallui/InCallServiceImpl.java
public void onCallAdded(Call call) {
Trace.beginSection("InCallServiceImpl.onCallAdded");
InCallPresenter.getInstance().onCallAdded(call);
Trace.endSection();
}
packages/apps/Dialer/java/com/android/incallui/InCallPresenter.java
public void onCallAdded(final android.telecom.Call call) {
Trace.beginSection("InCallPresenter.onCallAdded");
LatencyReport latencyReport = new LatencyReport(call);
if (shouldAttemptBlocking(call)) {
maybeBlockCall(call, latencyReport);
} else {
if (call.getDetails().hasProperty(CallCompat.Details.PROPERTY_IS_EXTERNAL_CALL)) {
externalCallList.onCallAdded(call);
} else {
latencyReport.onCallBlockingDone();
callList.onCallAdded(context, call, latencyReport);
}
}
// Since a call has been added we are no longer waiting for Telecom to send us a call.
setBoundAndWaitingForOutgoingCall(false, null);
call.registerCallback(callCallback);
// TODO(maxwelb): Return the future in recordPhoneLookupInfo and propagate.
PhoneLookupHistoryRecorder.recordPhoneLookupInfo(context.getApplicationContext(), call);
Trace.endSection();
}
7.InCallPresenter 通知 CallList 有call 被添加,则创建出 DialerCall 对象用于存储 Dialer 中的Call状态
packages/apps/Dialer/java/com/android/incallui/call/CallList.java
public void onCallAdded(
final Context context, final android.telecom.Call telecomCall, LatencyReport latencyReport) {
Trace.beginSection("CallList.onCallAdded");
if (telecomCall.getState() == Call.STATE_CONNECTING) {
MetricsComponent.get(context)
.metrics()
.startTimer(Metrics.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_OUTGOING);
} else if (telecomCall.getState() == Call.STATE_RINGING) {
MetricsComponent.get(context)
.metrics()
.startTimer(Metrics.ON_CALL_ADDED_TO_ON_INCALL_UI_SHOWN_INCOMING);
}
if (uiListeners != null) {
uiListeners.onCallAdded();
}
final DialerCall call =
new DialerCall(context, this, telecomCall, latencyReport, true /* registerCallback */);
if (getFirstCall() != null) {
logSecondIncomingCall(context, getFirstCall(), call);
}
EnrichedCallManager manager = EnrichedCallComponent.get(context).getEnrichedCallManager();
manager.registerCapabilitiesListener(call);
manager.registerStateChangedListener(call);
Trace.beginSection("checkSpam");
call.addListener(new DialerCallListenerImpl(call));
LogUtil.d("CallList.onCallAdded", "callState=" + call.getState());
if (SpamComponent.get(context).spamSettings().isSpamEnabled()) {
String number = TelecomCallUtil.getNumber(telecomCall);
ListenableFuture<SpamStatus> futureSpamStatus =
SpamComponent.get(context).spam().checkSpamStatus(number, call.getCountryIso());
Futures.addCallback(
futureSpamStatus,
new FutureCallback<SpamStatus>() {
@Override
public void onSuccess(@Nullable SpamStatus result) {
boolean isIncomingCall =
call.getState() == DialerCallState.INCOMING
|| call.getState() == DialerCallState.CALL_WAITING;
boolean isSpam = result.isSpam();
call.setSpamStatus(result);
if (isIncomingCall) {
Logger.get(context)
.logCallImpression(
isSpam
? DialerImpression.Type.INCOMING_SPAM_CALL
: DialerImpression.Type.INCOMING_NON_SPAM_CALL,
call.getUniqueCallId(),
call.getTimeAddedMs());
}
onUpdateCall(call);
notifyGenericListeners();
}
@Override
public void onFailure(Throwable t) {
LogUtil.e("CallList.onFailure", "unable to query spam status", t);
}
},
DialerExecutorComponent.get(context).uiExecutor());
Trace.beginSection("updateUserMarkedSpamStatus");
Trace.endSection();
}
Trace.endSection();
Trace.beginSection("checkBlock");
FilteredNumberAsyncQueryHandler filteredNumberAsyncQueryHandler =
new FilteredNumberAsyncQueryHandler(context);
filteredNumberAsyncQueryHandler.isBlockedNumber(
new FilteredNumberAsyncQueryHandler.OnCheckBlockedListener() {
@Override
public void onCheckComplete(Integer id) {
if (id != null && id != FilteredNumberAsyncQueryHandler.INVALID_ID) {
call.setBlockedStatus(true);
// No need to update UI since it's only used for logging.
}
}
},
call.getNumber(),
call.getCountryIso());
Trace.endSection();
if (call.getState() == DialerCallState.INCOMING
|| call.getState() == DialerCallState.CALL_WAITING) {
if (call.isActiveRttCall()) {
if (!call.isPhoneAccountRttCapable()) {
RttPromotion.setEnabled(context);
}
Logger.get(context)
.logCallImpression(
DialerImpression.Type.INCOMING_RTT_CALL,
call.getUniqueCallId(),
call.getTimeAddedMs());
}
onIncoming(call);
} else {
if (call.isActiveRttCall()) {
Logger.get(context)
.logCallImpression(
DialerImpression.Type.OUTGOING_RTT_CALL,
call.getUniqueCallId(),
call.getTimeAddedMs());
}
onUpdateCall(call);
notifyGenericListeners();
}
if (call.getState() != DialerCallState.INCOMING) {
// Only report outgoing calls
ShortcutUsageReporter.onOutgoingCallAdded(context, call.getNumber());
}
Trace.endSection();
}
8.DialerCall 注册监听 telecomCall 的状态,并根据其变化更新自身,随后将变化反映到UI 上
9.InCall UI 的显示通过 InCallPresenter 的 showInCall()方法实现
public void showInCall(boolean showDialpad, boolean newOutgoingCall) {
LogUtil.i("InCallPresenter.showInCall", "Showing InCallActivity");
context.startActivity(
InCallActivity.getIntent(context, showDialpad, newOutgoingCall, false /* forFullScreen */));
}
MO 拨号流程
1.最初的流程与 拉起 InCallUI类似,先处理 Call Inetnt, 然后CallIntentProcessor 的另一支流程会创建 NewOutgoingCallIntentBroadcaster
packages/services/Telecomm/src/com/android/server/telecom/CallIntentProcessor.java
static void processOutgoingCallIntent(
Context context,
CallsManager callsManager,
Intent intent,
String callingPackage,
DefaultDialerCache defaultDialerCache) {
Uri handle = intent.getData();
String scheme = handle.getScheme();
String uriString = handle.getSchemeSpecificPart();
...
boolean isPrivilegedDialer = defaultDialerCache.isDefaultOrSystemDialer(callingPackage,
initiatingUser.getIdentifier());
NewOutgoingCallIntentBroadcaster broadcaster = new NewOutgoingCallIntentBroadcaster(
context, callsManager, intent, callsManager.getPhoneNumberUtilsAdapter(),
isPrivilegedDialer, defaultDialerCache);
// evaluateCall()评估是否是紧急电话或voicemail
NewOutgoingCallIntentBroadcaster.CallDisposition disposition = broadcaster.evaluateCall();
if (disposition.disconnectCause != DisconnectCause.NOT_DISCONNECTED) {
showErrorDialog(context, disposition.disconnectCause);
return;
}
// Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
CompletableFuture<Call> callFuture = callsManager
.startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser,
intent, callingPackage);
final Session logSubsession = Log.createSubsession();
callFuture.thenAccept((call) -> {
if (call != null) {
Log.continueSession(logSubsession, "CIP.sNOCI");
try {
broadcaster.processCall(call, disposition);
} finally {
Log.endSession();
}
}
});
}
2.通过 NewOutgoingCallIntentBroadcaster 对象进行紧急电话的评估,然后进入NewOutgoingCallIntentBroadcaster 处理Call 的流程。
packages/services/Telecomm/src/com/android/server/telecom/NewOutgoingCallIntentBroadcaster.java
public void processCall(Call call, CallDisposition disposition) {
mCall = call;
if (disposition.callImmediately) {
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);
placeOutgoingCallImmediately(mCall, disposition.callingAddress, 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.
}
boolean callRedirectionWithService = false;
if (disposition.requestRedirection) {
CallRedirectionProcessor callRedirectionProcessor = new CallRedirectionProcessor(
mContext, mCallsManager, mCall, disposition.callingAddress,
mCallsManager.getPhoneAccountRegistrar(),
getGateWayInfoFromIntent(mIntent, mIntent.getData()),
mIntent.getBooleanExtra(TelecomManager.EXTRA_START_CALL_WITH_SPEAKERPHONE,
false),
mIntent.getIntExtra(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
VideoProfile.STATE_AUDIO_ONLY));
/**
* If there is an available {@link android.telecom.CallRedirectionService}, use the
* {@link CallRedirectionProcessor} to perform call redirection instead of using
* broadcasting.
*/
callRedirectionWithService = callRedirectionProcessor
.canMakeCallRedirectionWithService();
if (callRedirectionWithService) {
callRedirectionProcessor.performCallRedirection();
}
}
if (disposition.sendBroadcast) {
UserHandle targetUser = mCall.getInitiatingUser();
Log.i(this, "Sending NewOutgoingCallBroadcast for %s to %s", mCall, targetUser);
broadcastIntent(mIntent, disposition.number,
!disposition.callImmediately && !callRedirectionWithService, targetUser);
}
}
public class NewOutgoingCallBroadcastIntentReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
try {
Log.startSession("NOCBIR.oR");
Trace.beginSection("onReceiveNewOutgoingCallBroadcast");
synchronized (mLock) {
Log.v(this, "onReceive: %s", intent);
// Once the NEW_OUTGOING_CALL broadcast is finished, the resultData is
// used as the actual number to call. (If null, no call will be placed.)
String resultNumber = getResultData();
Log.i(this, "Received new-outgoing-call-broadcast for %s with data %s", mCall,
Log.pii(resultNumber));
boolean endEarly = false;
long disconnectTimeout =
Timeouts.getNewOutgoingCallCancelMillis(mContext.getContentResolver());
if (resultNumber == null) {
Log.v(this, "Call cancelled (null number), returning...");
disconnectTimeout = getDisconnectTimeoutFromApp(
getResultExtras(false), disconnectTimeout);
endEarly = true;
} else if (isPotentialEmergencyNumber(resultNumber)) {
Log.w(this, "Cannot modify outgoing call to emergency number %s.",
resultNumber);
disconnectTimeout = 0;
endEarly = true;
}
if (endEarly) {
if (mCall != null) {
mCall.disconnect(disconnectTimeout);
}
return;
}
// If this call is already disconnected then we have nothing more to do.
if (mCall.isDisconnected()) {
Log.w(this, "Call has already been disconnected," +
" ignore the broadcast Call %s", mCall);
return;
}
// TODO: Remove the assumption that phone numbers are either SIP or TEL.
// This does not impact self-managed ConnectionServices as they do not use the
// NewOutgoingCallIntentBroadcaster.
Uri resultHandleUri = Uri.fromParts(
mPhoneNumberUtilsAdapter.isUriNumber(resultNumber) ?
PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL,
resultNumber, null);
Uri originalUri = mIntent.getData();
if (originalUri.getSchemeSpecificPart().equals(resultNumber)) {
Log.v(this, "Call number unmodified after" +
" new outgoing call intent broadcast.");
} else {
Log.v(this, "Retrieved modified handle after outgoing call intent" +
" broadcast: Original: %s, Modified: %s",
Log.pii(originalUri),
Log.pii(resultHandleUri));
}
GatewayInfo gatewayInfo = getGateWayInfoFromIntent(intent, resultHandleUri);
placeOutgoingCallImmediately(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();
}
}
}
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);
final BroadcastOptions options = BroadcastOptions.makeBasic();
options.setBackgroundActivityStartsAllowed(true);
mContext.sendOrderedBroadcastAsUser(
broadcastIntent,
targetUser,
android.Manifest.permission.PROCESS_OUTGOING_CALLS,
AppOpsManager.OP_PROCESS_OUTGOING_CALLS,
options.toBundle(),
receiverRequired ? new NewOutgoingCallBroadcastIntentReceiver() : null,
null, // scheduler
Activity.RESULT_OK, // initialCode
number, // initialData: initial value for the result data (number to be modified)
null); // initialExtras
}
private void placeOutgoingCallImmediately(Call call, Uri handle, GatewayInfo gatewayInfo,
boolean speakerphoneOn, int videoState) {
Log.i(this,
"Placing call immediately instead of waiting for OutgoingCallBroadcastReceiver");
// Since we are not going to go through "Outgoing call broadcast", make sure
// we mark it as ready.
mCall.setNewOutgoingCallIntentBroadcastIsDone();
mCallsManager.placeOutgoingCall(call, handle, gatewayInfo, speakerphoneOn, videoState);
}
3.NewOutgoingCallIntentBroadcaster 中会根据电话是否是紧急拨号决定是否简化拨号流程。 正常拨号是通过CallsManager 的 placeOutgoingCall 方法,通过 Call 对象方法 startCreateConnection 来创建连接
packages/services/Telecomm/src/com/android/server/telecom/CallsManager.java
public void placeOutgoingCall(Call call, Uri handle, GatewayInfo gatewayInfo,
boolean speakerphoneOn, int videoState) {
if (call == null) {
// don't do anything if the call no longer exists
Log.i(this, "Canceling unknown call.");
return;
}
final Uri uriHandle = (gatewayInfo == null) ? handle : gatewayInfo.getGatewayAddress();
if (gatewayInfo == null) {
Log.i(this, "Creating a new outgoing call with handle: %s", Log.piiHandle(uriHandle));
} else {
Log.i(this, "Creating a new outgoing call with gateway handle: %s, original handle: %s",
Log.pii(uriHandle), Log.pii(handle));
}
call.setHandle(uriHandle);
call.setGatewayInfo(gatewayInfo);
final boolean useSpeakerWhenDocked = mContext.getResources().getBoolean(
R.bool.use_speaker_when_docked);
final boolean useSpeakerForDock = isSpeakerphoneEnabledForDock();
final boolean useSpeakerForVideoCall = isSpeakerphoneAutoEnabledForVideoCalls(videoState);
// Auto-enable speakerphone if the originating intent specified to do so, if the call
// is a video call, of if using speaker when docked
PhoneAccount account = mPhoneAccountRegistrar.getPhoneAccount(
call.getTargetPhoneAccount(), call.getInitiatingUser());
boolean allowVideo = false;
if (account != null) {
allowVideo = account.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING);
}
call.setStartWithSpeakerphoneOn(speakerphoneOn || (useSpeakerForVideoCall && allowVideo)
|| (useSpeakerWhenDocked && useSpeakerForDock));
call.setVideoState(videoState);
if (speakerphoneOn) {
Log.i(this, "%s Starting with speakerphone as requested", call);
} else if (useSpeakerWhenDocked && useSpeakerForDock) {
Log.i(this, "%s Starting with speakerphone because car is docked.", call);
} else if (useSpeakerForVideoCall) {
Log.i(this, "%s Starting with speakerphone because its a video call.", call);
}
if (call.isEmergencyCall()) {
Executors.defaultThreadFactory().newThread(() ->
BlockedNumberContract.SystemContract.notifyEmergencyContact(mContext))
.start();
}
final boolean requireCallCapableAccountByHandle = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_requireCallCapableAccountForHandle);
final boolean isOutgoingCallPermitted = isOutgoingCallPermitted(call,
call.getTargetPhoneAccount());
final String callHandleScheme =
call.getHandle() == null ? null : call.getHandle().getScheme();
if (call.getTargetPhoneAccount() != null || call.isEmergencyCall()) {
// If the account has been set, proceed to place the outgoing call.
// Otherwise the connection will be initiated when the account is set by the user.
if (call.isSelfManaged() && !isOutgoingCallPermitted) {
if (call.isAdhocConferenceCall()) {
notifyCreateConferenceFailed(call.getTargetPhoneAccount(), call);
} else {
notifyCreateConnectionFailed(call.getTargetPhoneAccount(), call);
}
} else {
if (call.isEmergencyCall()) {
// Drop any ongoing self-managed calls to make way for an emergency call.
disconnectSelfManagedCalls("place emerg call" /* reason */);
}
call.startCreateConnection(mPhoneAccountRegistrar);
}
} else if (mPhoneAccountRegistrar.getCallCapablePhoneAccounts(
requireCallCapableAccountByHandle ? callHandleScheme : null, false,
call.getInitiatingUser()).isEmpty()) {
// If there are no call capable accounts, disconnect the call.
markCallAsDisconnected(call, new DisconnectCause(DisconnectCause.CANCELED,
"No registered PhoneAccounts"));
markCallAsRemoved(call);
}
}
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();
}
4.创建连接需通过 CreateConnectionProcessor来进行,首先选择PhoneAccount,接下来主要分为了两步:【1】创建ConnectionService,【2】通过ConnectionService创建 Connection。
具体步骤为使用 ConnectionServiceWrapper 来绑定 ConnectionService, 使用其子类 TelephonyConnectionService 来实现实际创建 Connection的 逻辑。Connection 也有许多子实现,首先确定拨打电话需要创建 TelephonyConnection,之后根据Phone 的type 创建出 GsmConnection或CdmaConnection
packages/services/Telecomm/src/com/android/server/telecom/CreateConnectionProcessor.java
public void process() {
Log.v(this, "process");
clearTimeout();
mAttemptRecords = new ArrayList<>();
if (mCall.getTargetPhoneAccount() != null) {
mAttemptRecords.add(new CallAttemptRecord(
mCall.getTargetPhoneAccount(), mCall.getTargetPhoneAccount()));
}
if (!mCall.isSelfManaged()) {
adjustAttemptsForConnectionManager();
adjustAttemptsForEmergency(mCall.getTargetPhoneAccount());
}
mAttemptRecordIterator = mAttemptRecords.iterator();
attemptNextPhoneAccount();
}
private void attemptNextPhoneAccount() {
Log.v(this, "attemptNextPhoneAccount");
CallAttemptRecord attempt = null;
if (mAttemptRecordIterator.hasNext()) {
attempt = mAttemptRecordIterator.next();
if (!mPhoneAccountRegistrar.phoneAccountRequiresBindPermission(
attempt.connectionManagerPhoneAccount)) {
Log.w(this,
"Connection mgr does not have BIND_TELECOM_CONNECTION_SERVICE for "
+ "attempt: %s", attempt);
attemptNextPhoneAccount();
return;
}
// If the target PhoneAccount differs from the ConnectionManager phone acount, ensure it
// also requires the BIND_TELECOM_CONNECTION_SERVICE permission.
if (!attempt.connectionManagerPhoneAccount.equals(attempt.targetPhoneAccount) &&
!mPhoneAccountRegistrar.phoneAccountRequiresBindPermission(
attempt.targetPhoneAccount)) {
Log.w(this,
"Target PhoneAccount does not have BIND_TELECOM_CONNECTION_SERVICE for "
+ "attempt: %s", attempt);
attemptNextPhoneAccount();
return;
}
}
if (mCallResponse != null && attempt != null) {
Log.i(this, "Trying attempt %s", attempt);
PhoneAccountHandle phoneAccount = attempt.connectionManagerPhoneAccount;
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);
if (mCall.isIncoming()) {
if (mCall.isAdhocConferenceCall()) {
mService.createConference(mCall, CreateConnectionProcessor.this);
} else {
mService.createConnection(mCall, CreateConnectionProcessor.this);
}
} else {
// Start to create the connection for outgoing call after the ConnectionService
// of the call has gained the focus.
mCall.getConnectionServiceFocusManager().requestFocus(
mCall,
new CallsManager.RequestCallback(new CallsManager.PendingAction() {
@Override
//ConnectionServiceFocusManager回调方法onRequestFocusDone会调用performAction
public void performAction() {
if (mCall.isAdhocConferenceCall()) {
Log.d(this, "perform create conference");
mService.createConference(mCall,
CreateConnectionProcessor.this);
} else {
Log.d(this, "perform create connection");
//mService是ConnectionServiceWrapper对象,
mService.createConnection(
mCall,
CreateConnectionProcessor.this);
}
}
}));
}
}
} else {
Log.v(this, "attemptNextPhoneAccount, no more accounts, failing");
DisconnectCause disconnectCause = mLastErrorDisconnectCause != null ?
mLastErrorDisconnectCause : new DisconnectCause(DisconnectCause.ERROR);
if (mCall.isAdhocConferenceCall()) {
notifyConferenceCallFailure(disconnectCause);
} else {
notifyCallConnectionFailure(disconnectCause);
}
}
}
packages/services/Telecomm/src/com/android/server/telecom/ConnectionServiceWrapper.java
public void createConnection(final Call call, final CreateConnectionResponse response) {
Log.i(this, "createConnection(%s) via %s.", call, getComponentName());
BindCallback callback = new BindCallback() {
@Override
public void onSuccess() {
String callId = mCallIdMapper.getCallId(call);
if (callId == null) {
Log.w(ConnectionServiceWrapper.this, "Call not present"
+ " in call id mapper, maybe it was aborted before the bind"
+ " completed successfully?");
response.handleCreateConnectionFailure(
new DisconnectCause(DisconnectCause.CANCELED));
return;
}
mPendingResponses.put(callId, response);
GatewayInfo gatewayInfo = call.getGatewayInfo();
Bundle extras = call.getIntentExtras();
if (gatewayInfo != null && gatewayInfo.getGatewayProviderPackageName() != null &&
gatewayInfo.getOriginalAddress() != null) {
extras = (Bundle) extras.clone();
extras.putString(
TelecomManager.GATEWAY_PROVIDER_PACKAGE,
gatewayInfo.getGatewayProviderPackageName());
extras.putParcelable(
TelecomManager.GATEWAY_ORIGINAL_ADDRESS,
gatewayInfo.getOriginalAddress());
}
if (call.isIncoming() && mCallsManager.getEmergencyCallHelper()
.getLastEmergencyCallTimeMillis() > 0) {
// Add the last emergency call time to the connection request for incoming calls
if (extras == call.getIntentExtras()) {
extras = (Bundle) extras.clone();
}
extras.putLong(android.telecom.Call.EXTRA_LAST_EMERGENCY_CALLBACK_TIME_MILLIS,
mCallsManager.getEmergencyCallHelper().getLastEmergencyCallTimeMillis());
}
// Call is incoming and added because we're handing over from another; tell CS
// that its expected to handover.
if (call.isIncoming() && call.getHandoverSourceCall() != null) {
extras.putBoolean(TelecomManager.EXTRA_IS_HANDOVER, true);
extras.putParcelable(TelecomManager.EXTRA_HANDOVER_FROM_PHONE_ACCOUNT,
call.getHandoverSourceCall().getTargetPhoneAccount());
}
Log.addEvent(call, LogUtils.Events.START_CONNECTION,
Log.piiHandle(call.getHandle()) + " via:" +
getComponentName().getPackageName());
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(TELECOM_ABBREVIATION));
} 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是ServiceBinder.Binder2的实例
mBinder.bind(callback, call);
}
packages/services/Telecomm/src/com/android/server/telecom/ServiceBinder.java
public abstract class ServiceBinder {
final class Binder2 {
/**
* Performs an asynchronous bind to the service (only if not already bound) and executes the
* specified callback.
*
* @param callback The callback to notify of the binding's success or failure.
* @param call The call for which we are being bound.
*/
void bind(BindCallback callback, Call call) {
Log.d(ServiceBinder.this, "bind()");
// Reset any abort request if we're asked to bind again.
clearAbort();
synchronized (mCallbacks) {
if (!mCallbacks.isEmpty()) {
// Binding already in progress, append to the list of callbacks and bail out.
mCallbacks.add(callback);
return;
}
mCallbacks.add(callback);
}
if (mServiceConnection == null) {
Intent serviceIntent = new Intent(mServiceAction).setComponent(mComponentName);
ServiceConnection connection = new ServiceBinderConnection(call);
Log.addEvent(call, LogUtils.Events.BIND_CS, mComponentName);
final int bindingFlags = Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE
| Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS;
final boolean isBound;
if (mUserHandle != null) {
isBound = mContext.bindServiceAsUser(serviceIntent, connection, bindingFlags,
mUserHandle);
} else {
isBound = mContext.bindService(serviceIntent, connection, bindingFlags);
}
if (!isBound) {
handleFailedConnection();
return;
}
} else {
Log.d(ServiceBinder.this, "Service is already bound.");
Preconditions.checkNotNull(mBinder);
handleSuccessfulConnection();
}
}
}
private final class ServiceBinderConnection implements ServiceConnection {
/**
* The initial call for which the service was bound.
*/
private Call mCall;
ServiceBinderConnection(Call call) {
mCall = call;
}
@Override
public void onServiceConnected(ComponentName componentName, IBinder binder) {
try {
Log.startSession("SBC.oSC", Log.getPackageAbbreviation(componentName));
synchronized (mLock) {
Log.i(this, "Service bound %s", componentName);
Log.addEvent(mCall, LogUtils.Events.CS_BOUND, componentName);
// Unbind request was queued so unbind immediately.
if (mIsBindingAborted) {
clearAbort();
logServiceDisconnected("onServiceConnected");
mContext.unbindService(this);
handleFailedConnection();
return;
}
if (binder != null) {
mServiceDeathRecipient = new ServiceDeathRecipient(componentName);
try {
binder.linkToDeath(mServiceDeathRecipient, 0);
mServiceConnection = this;
setBinder(binder); //保存绑定服务成功后的binder对象
handleSuccessfulConnection(); //bind成功后处理
} catch (RemoteException e) {
Log.w(this, "onServiceConnected: %s died.");
if (mServiceDeathRecipient != null) {
mServiceDeathRecipient.binderDied();
}
}
}
}
} finally {
Log.endSession();
}
}
}
private void setBinder(IBinder binder) {
if (mBinder != binder) {
if (binder == null) {
removeServiceInterface();
mBinder = null;
for (Listener l : mListeners) {
l.onUnbind(this);
}
} else {
mBinder = binder;
setServiceInterface(binder);
}
}
}
}
protected abstract void setServiceInterface(IBinder binder); //抽象方法,在ConnectionServiceWrapper实现。
packages/services/Telecomm/src/com/android/server/telecom/ConnectionServiceWrapper.java
protected void setServiceInterface(IBinder binder) {
mServiceInterface = IConnectionService.Stub.asInterface(binder);
Log.v(this, "Adding Connection Service Adapter.");
addConnectionServiceAdapter(mAdapter);
}
private void addConnectionServiceAdapter(IConnectionServiceAdapter adapter) {
if (isServiceValid("addConnectionServiceAdapter")) {
try {
logOutgoing("addConnectionServiceAdapter %s", adapter);
mServiceInterface.addConnectionServiceAdapter(adapter, Log.getExternalSession());
} catch (RemoteException e) {
}
}
}
frameworks/base/telecomm/java/android/telecom/ConnectionService.java
public abstract class ConnectionService extends Service {
private final IBinder mBinder = new IConnectionService.Stub() {
@Override
public void addConnectionServiceAdapter(IConnectionServiceAdapter adapter,
Session.Info sessionInfo) {
Log.startSession(sessionInfo, SESSION_ADD_CS_ADAPTER);
try {
SomeArgs args = SomeArgs.obtain();
args.arg1 = adapter;
args.arg2 = Log.createSubsession();
mHandler.obtainMessage(MSG_ADD_CONNECTION_SERVICE_ADAPTER, args).sendToTarget();
} finally {
Log.endSession();
}
}
}
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ADD_CONNECTION_SERVICE_ADAPTER: {
SomeArgs args = (SomeArgs) msg.obj;
try {
IConnectionServiceAdapter adapter = (IConnectionServiceAdapter) args.arg1;
Log.continueSession((Session) args.arg2,
SESSION_HANDLER + SESSION_ADD_CS_ADAPTER);
mAdapter.addAdapter(adapter);
onAdapterAttached();
} finally {
args.recycle();
Log.endSession();
}
break;
...
}
}
}
}
}
frameworks/base/telecomm/java/android/telecom/ConnectionServiceAdapter.java
void addAdapter(IConnectionServiceAdapter adapter) {
for (IConnectionServiceAdapter it : mAdapters) {
if (it.asBinder() == adapter.asBinder()) {
Log.w(this, "Ignoring duplicate adapter addition.");
return;
}
}
if (mAdapters.add(adapter)) {
try {
adapter.asBinder().linkToDeath(this, 0);
} catch (RemoteException e) {
mAdapters.remove(adapter);
}
}
}
packages/services/Telecomm/src/com/android/server/telecom/ServiceBinder.java
private void handleSuccessfulConnection() {
// Make a copy so that we don't have a deadlock inside one of the callbacks.
Set<BindCallback> callbacksCopy = new ArraySet<>();
synchronized (mCallbacks) {
callbacksCopy.addAll(mCallbacks);
mCallbacks.clear();
}
for (BindCallback callback : callbacksCopy) {
callback.onSuccess();
}
}
BindCallback匿名实现在ConnectionServiceWrapper中的createConnection方法中。
packages/services/Telecomm/src/com/android/server/telecom/ConnectionServiceWrapper.java
public void createConnection(final Call call, final CreateConnectionResponse response) {
Log.i(this, "createConnection(%s) via %s.", call, getComponentName());
BindCallback callback = new BindCallback() {
@Override
public void onSuccess() {
String callId = mCallIdMapper.getCallId(call);
...
try {
mServiceInterface.createConnection(
call.getConnectionManagerPhoneAccount(),
callId,
connectionRequest,
call.shouldAttachToExistingConnection(),
call.isUnknown(),
Log.getExternalSession(TELECOM_ABBREVIATION));
} catch (RemoteException e) {
Log.e(this, e, "Failure to createConnection -- %s", getComponentName());
mPendingResponses.remove(callId).handleCreateConnectionFailure(
new DisconnectCause(DisconnectCause.ERROR, e.toString()));
}
}
};
mBinder.bind(callback, call);
}
frameworks/base/telecomm/java/android/telecom/ConnectionService.java
public abstract class ConnectionService extends Service {
private final IBinder mBinder = new IConnectionService.Stub() {
public void createConnection(
PhoneAccountHandle connectionManagerPhoneAccount,
String id,
ConnectionRequest request,
boolean isIncoming,
boolean isUnknown,
Session.Info sessionInfo) {
Log.startSession(sessionInfo, SESSION_CREATE_CONN);
try {
SomeArgs args = SomeArgs.obtain();
args.arg1 = connectionManagerPhoneAccount;
args.arg2 = id;
args.arg3 = request;
args.arg4 = Log.createSubsession();
args.argi1 = isIncoming ? 1 : 0;
args.argi2 = isUnknown ? 1 : 0;
mHandler.obtainMessage(MSG_CREATE_CONNECTION, args).sendToTarget();
} finally {
Log.endSession();
}
}
}
private final Handler mHandler = new Handler(Looper.getMainLooper()) {
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_CREATE_CONNECTION: {
SomeArgs args = (SomeArgs) msg.obj;
Log.continueSession((Session) args.arg4, SESSION_HANDLER + SESSION_CREATE_CONN);
try {
final PhoneAccountHandle connectionManagerPhoneAccount =
(PhoneAccountHandle) args.arg1;
final String id = (String) args.arg2;
final ConnectionRequest request = (ConnectionRequest) args.arg3;
final boolean isIncoming = args.argi1 == 1;
final boolean isUnknown = args.argi2 == 1;
if (!mAreAccountsInitialized) {
...
} else {
//调用onCreateOutgoingConnection创建Connection
createConnection(
connectionManagerPhoneAccount,
id,
request,
isIncoming,
isUnknown);
}
} finally {
args.recycle();
Log.endSession();
}
break;
}
}
}
}
protected void createConnection(
final PhoneAccountHandle callManagerAccount,
final String callId,
final ConnectionRequest request,
boolean isIncoming,
boolean isUnknown) {
...
if (isHandover) {
...
} else {
connection = isUnknown ? onCreateUnknownConnection(callManagerAccount, request)
: isIncoming ? onCreateIncomingConnection(callManagerAccount, request)
: onCreateOutgoingConnection(callManagerAccount, request);
}
Log.d(this, "createConnection, connection: %s", connection);
if (connection == null) {
Log.i(this, "createConnection, implementation returned null connection.");
connection = Connection.createFailedConnection(
new DisconnectCause(DisconnectCause.ERROR, "IMPL_RETURNED_NULL_CONNECTION"));
} else {
...
}
...
if (isUnknown) {
triggerConferenceRecalculate();
}
}
}
packages/services/Telephony/src/com/android/services/telephony/TelephonyConnectionService.java
public class TelephonyConnectionService extends ConnectionService {
public Connection onCreateOutgoingConnection(
PhoneAccountHandle connectionManagerPhoneAccount,
final ConnectionRequest request) {
Log.i(this, "onCreateOutgoingConnection, request: " + request);
...
if (needToTurnOnRadio) {
...
} else {
...
if (!isEmergencyNumber) {
final Connection resultConnection = getTelephonyConnection(request, numberToDial,
false, handle, phone);
if (isAdhocConference) {
if (resultConnection instanceof TelephonyConnection) {
TelephonyConnection conn = (TelephonyConnection) resultConnection;
conn.setParticipants(request.getParticipants());
}
return resultConnection;
} else {
return placeOutgoingConnection(request, resultConnection, phone);
}
} else {
...
}
}
}
private Connection getTelephonyConnection(final ConnectionRequest request, final String number,
boolean isEmergencyNumber, final Uri handle, Phone phone) {
...
PhoneAccountHandle accountHandle = adjustAccountHandle(phone, request.getAccountHandle());
//根据phone的gsm/cdma/sip类型创建对应的connection.im不在这里创建。
final TelephonyConnection connection =
createConnectionFor(phone, null, true /* isOutgoing */, accountHandle,
request.getTelecomCallId(), request.isAdhocConferenceCall());
if (connection == null) {
return Connection.createFailedConnection(
mDisconnectCauseFactory.toTelecomDisconnectCause(
android.telephony.DisconnectCause.OUTGOING_FAILURE,
"Invalid phone type",
phone.getPhoneId()));
}
...
return connection;
}
private void placeOutgoingConnection(
TelephonyConnection connection, Phone phone, int videoState, Bundle extras) {
...
final com.android.internal.telephony.Connection originalConnection;
try {
if (phone != null) {
EmergencyNumber emergencyNumber =
phone.getEmergencyNumberTracker().getEmergencyNumber(number);
...
//调用GsmCdmaPhone.dial()
originalConnection = phone.dial(number, new ImsPhone.ImsDialArgs.Builder()
.setVideoState(videoState)
.setIntentExtras(extras)
.setRttTextStream(connection.getRttTextStream())
.build(),
// We need to wait until the phone has been chosen in GsmCdmaPhone to
// register for the associated TelephonyConnection call event listeners.
connection::registerForCallEvents);
} else {
originalConnection = null;
}
} catch (CallStateException e) {
Log.e(this, e, "placeOutgoingConnection, phone.dial exception: " + e);
connection.unregisterForCallEvents();
handleCallStateException(e, connection, phone);
return;
}
...
}
}
5.Connection 创建完毕, TelephonyConnectionService 调用 placeOutgoingConnection 方法,通过Phone 对象调用 dial()方法进行拨号,其实调用的是执行顺序是GsmCdmaPhone.dial(),在GsmCdmaPhone 对象的拨号方法中会判断 ImsPhone对象是否存在,并根据相关配置决定是否要用ImsPhone对象来进行拨号 ;
frameworks/opt/telephony/src/java/com/android/internal/telephony/GsmCdmaPhone.java
public class GsmCdmaPhone extends Phone {
public Connection dial(String dialString, @NonNull DialArgs dialArgs,
Consumer<Phone> chosenPhoneConsumer) throws CallStateException {
...
if (imsPhone != null && !allowWpsOverIms && !useImsForCall && isWpsCall
&& imsPhone.getCallTracker() instanceof ImsPhoneCallTracker) {
logi("WPS call placed over CS; disconnecting all IMS calls..");
ImsPhoneCallTracker tracker = (ImsPhoneCallTracker) imsPhone.getCallTracker();
tracker.hangupAllConnections();
}
if ((useImsForCall && (!isMmiCode || isPotentialUssdCode))
|| (isMmiCode && useImsForUt)
|| useImsForEmergency) {
try {
if (DBG) logd("Trying IMS PS call");
chosenPhoneConsumer.accept(imsPhone);
//调用ImsPhone.dial();
return imsPhone.dial(dialString, dialArgs);
} catch (CallStateException e) {
if (DBG) logd("IMS PS call exception " + e +
"useImsForCall =" + useImsForCall + ", imsPhone =" + imsPhone);
// Do not throw a CallStateException and instead fall back to Circuit switch
// for emergency calls and MMI codes.
if (Phone.CS_FALLBACK.equals(e.getMessage()) || isEmergency) {
logi("IMS call failed with Exception: " + e.getMessage() + ". Falling back "
+ "to CS.");
} else {
CallStateException ce = new CallStateException(e.getError(), e.getMessage());
ce.setStackTrace(e.getStackTrace());
throw ce;
}
}
}
...
}
}
ImsPhone.dial()-> ImsPhone.dialInternal() -> ImsPhoneCallTracker.dial() -> ImsPhoneCallTracker.dialInternal
frameworks/opt/net/ims/src/java/com/android/ims/ImsManager.java
mImsManager.makeCall()
public ImsCall makeCall(ImsCallProfile profile, String[] callees,
ImsCall.Listener listener) throws ImsException {
if (DBG) {
log("makeCall :: profile=" + profile);
}
// Check we are still alive
getOrThrowExceptionIfServiceUnavailable();
ImsCall call = new ImsCall(mContext, profile);
call.setListener(listener);
ImsCallSession session = createCallSession(profile);
if ((callees != null) && (callees.length == 1) && !(session.isMultiparty())) {
call.start(session, callees[0]);
} else {
call.start(session, callees);
}
return call;
}
frameworks/base/telephony/java/android/telephony/ims/ImsCallSession.java
public void start(String callee, ImsCallProfile profile) {
if (mClosed) {
return;
}
try {
miSession.start(callee, profile);
} catch (RemoteException e) {
}
}
frameworks/base/telephony/java/android/telephony/ims/stub/ImsCallSessionImplBase.java
public void start(String callee, ImsCallProfile profile) {
executeMethodAsync(() -> ImsCallSessionImplBase.this.start(callee, profile), "start");
}
6.之后就是 GsmCdmaPhoneCallTracker / ImsPhoneCallTracker 对象与底层交互的流程。【1】如果是使用 GsmCdmaPhone对象拨号,GsmCdmaPhoneCallTracker中会与 RILJ 交互;【2】如果是使用ImsPhone对象拨号,则需要通过 ImsManager 调用到各个平台自行实现的 Ims.apk来完成拨号流程
电话挂断初步排查
在notepad中用查找模式为正则表达式的方式搜索一下信息:
placeCall|termina|onDisconnect|DisconnectCause|InCallAda|HANGUP|REQUEST_DISCONNECT|DIALING_ENDED|BOUNCE_HANG_UP_BUTTON_PRESSED|incallada
底层挂断,modem检查
呼出未接通,手动挂断
来电没接听,对端挂断
未呼出,网络问题,需要对比测试