在分析
android N拨打电话流程中分析了,从拨号盘到RIL.java的流程,没有分析拨号过程和UI的交互,这篇文章来说明,拉起InCallUI的过程.
在上篇文章中可以知道CallIntentProcessor中会通过
processOutgoingCallIntent来调用
CallManager的startOutgoingCall和NewOutgoingCallIntentBroadcaster的processIntent方法,分析就是从
startOutgoingCall开始的.
1.
CallManager的startOutgoingCall
Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras,
UserHandle initiatingUser) {
boolean isReusedCall = true;
Call call = reuseOutgoingCall(handle);
// Create a call with original handle. The handle may be changed when the call is attached
// to a connection service, but in most cases will remain the same.
if (call == null) {
call = new Call(getNextCallId(), mContext,
this,
mLock,
mConnectionServiceRepository,
mContactsAsyncHelper,
mCallerInfoAsyncQueryFactory,
mPhoneNumberUtilsAdapter,
handle,
null /* gatewayInfo */,
null /* connectionManagerPhoneAccount */,
null /* phoneAccountHandle */,
Call.CALL_DIRECTION_OUTGOING /* callDirection */,
false /* forceAttachToExistingConnection */,
false /* isConference */
);
if ((extras != null) &&
extras.getBoolean(TelephonyProperties.EXTRA_DIAL_CONFERENCE_URI, false)) {
//Reset PostDialDigits with empty string for ConfURI call.
call.setPostDialDigits("");
}
call.initAnalytics();
call.setInitiatingUser(initiatingUser);
isReusedCall = false;
}
// Set the video state on the call early so that when it is added to the InCall UI the UI
// knows to configure itself as a video call immediately.
if (extras != null) {
int videoState = extras.getInt(TelecomManager.EXTRA_START_CALL_WITH_VIDEO_STATE,
VideoProfile.STATE_AUDIO_ONLY);
// If this is an emergency video call, we need to check if the phone account supports
// emergency video calling.
// Also, ensure we don't try to place an outgoing call with video if video is not
// supported.
if (VideoProfile.isVideo(videoState)) {
PhoneAccount account =
mPhoneAccountRegistrar.getPhoneAccount(phoneAccountHandle, initiatingUser);
if (call.isEmergencyCall() && account != null &&
!account.hasCapabilities(PhoneAccount.CAPABILITY_EMERGENCY_VIDEO_CALLING)) {
// Phone account doesn't support emergency video calling, so fallback to
// audio-only now to prevent the InCall UI from setting up video surfaces
// needlessly.
Log.i(this, "startOutgoingCall - emergency video calls not supported; " +
"falling back to audio-only");
videoState = VideoProfile.STATE_AUDIO_ONLY;
} else if (account != null &&
!account.hasCapabilities(PhoneAccount.CAPABILITY_VIDEO_CALLING)) {
// Phone account doesn't support video calling, so fallback to audio-only.
Log.i(this, "startOutgoingCall - video calls not supported; fallback to " +
"audio-only.");
videoState = VideoProfile.STATE_AUDIO_ONLY;
}
}
call.setVideoState(videoState);
}
boolean isAddParticipant = ((extras != null) && (extras.getBoolean(
TelephonyProperties.ADD_PARTICIPANT_KEY, false)));
boolean isSkipSchemaOrConfUri = ((extras != null) && (extras.getBoolean(
TelephonyProperties.EXTRA_SKIP_SCHEMA_PARSING, false) ||
extras.getBoolean(TelephonyProperties.EXTRA_DIAL_CONFERENCE_URI, false)));
if (isAddParticipant) {
String number = handle.getSchemeSpecificPart();
if (!isSkipSchemaOrConfUri) {
number = PhoneNumberUtils.stripSeparators(number);
}
addParticipant(number);
mInCallController.bringToForeground(false);
return null;
}
// Force tel scheme for ims conf uri/skip schema calls to avoid selection of sip accounts
String scheme = (isSkipSchemaOrConfUri? PhoneAccount.SCHEME_TEL: handle.getScheme());
Log.d(this, "startOutgoingCall :: isAddParticipant=" + isAddParticipant
+ " isSkipSchemaOrConfUri=" + isSkipSchemaOrConfUri + " scheme=" + scheme);
List accounts =
constructPossiblePhoneAccounts(handle, initiatingUser, scheme);
Log.v(this, "startOutgoingCall found accounts = " + accounts);
// Only dial with the requested phoneAccount if it is still valid. Otherwise treat this call
// as if a phoneAccount was not specified (does the default behavior instead).
// Note: We will not attempt to dial with a requested phoneAccount if it is disabled.
if (phoneAccountHandle != null) {
if (!accounts.contains(phoneAccountHandle)) {
phoneAccountHandle = null;
}
}
if (phoneAccountHandle == null && accounts.size() > 0) {
// No preset account, check if default exists that supports the URI scheme for the
// handle and verify it can be used.
if(accounts.size() > 1) {
PhoneAccountHandle defaultPhoneAccountHandle =
mPhoneAccountRegistrar.getOutgoingPhoneAccountForScheme(scheme,
initiatingUser);
if (defaultPhoneAccountHandle != null &&
accounts.contains(defaultPhoneAccountHandle)) {
phoneAccountHandle = defaultPhoneAccountHandle;
}
} else {
// Use the only PhoneAccount that is available
phoneAccountHandle = accounts.get(0);
}
}
call.setTargetPhoneAccount(phoneAccountHandle);
boolean isPotentialInCallMMICode = isPotentialInCallMMICode(handle);
// Do not support any more live calls. Our options are to move a call to hold, disconnect
// a call, or cancel this call altogether. If a call is being reused, then it has already
// passed the makeRoomForOutgoingCall check once and will fail the second time due to the
// call transitioning into the CONNECTING state.
if (!isPotentialInCallMMICode && (!isReusedCall &&
!makeRoomForOutgoingCall(call, call.isEmergencyCall()))) {
// just cancel at this point.
Log.i(this, "No remaining room for outgoing call: %s", call);
if (mCalls.contains(call)) {
// This call can already exist if it is a reused call,
// See {@link #reuseOutgoingCall}.
call.disconnect();
}
return null;
}
boolean needsAccountSelection = phoneAccountHandle == null && accounts.size() > 1 &&
!call.isEmergencyCall();
if (needsAccountSelection) {
// This is the state where the user is expected to select an account
call.setState(CallState.SELECT_PHONE_ACCOUNT, "needs account selection");
// Create our own instance to modify (since extras may be Bundle.EMPTY)
extras = new Bundle(extras);
extras.putParcelableList(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS, accounts);
} else {
call.setState(
CallState.CONNECTING,
phoneAccountHandle == null ? "no-handle" : phoneAccountHandle.toString());
}
setIntentExtrasAndStartTime(call, extras);
// Do not add the call if it is a potential MMI code.
if ((isPotentialMMICode(handle) || isPotentialInCallMMICode) && !needsAccountSelection) {
call.addListener(this);
// If call is Emergency type and marked it as Pending, call would not be added
// in mCalls here. It will be handled when the current active call (mDisconnectingCall)
// is disconnected successfully.
} else if (!mCalls.contains(call) && mPendingMOEmerCall == null) {
// We check if mCalls already contains the call because we could potentially be reusing
// a call which was previously added (See {@link #reuseOutgoingCall}).
addCall(call);
}
return call;
}
上篇文章已经分析过了,重点创建一个Call对象,但是却没有分析该函数的尾部有个addCall操作,addCall之后会有什么操作?
2
addCall
private void addCall(Call call) {
Trace.beginSection("addCall");
Log.v(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();
// onCallAdded for calls which immediately take the foreground (like the first call).
for (CallsManagerListener listener : mListeners) {
if (Log.SYSTRACE_DEBUG) {
Trace.beginSection(listener.getClass().toString() + " addCall");
}
listener.onCallAdded(call);
if (Log.SYSTRACE_DEBUG) {
Trace.endSection();
}
}
Trace.endSection();
}
该处是观察者模式,会通知到观察者listener.onCallAdded(call),其中我们看看都有哪些对象注册了监听.
mListeners.add(mInCallWakeLockController);
mListeners.add(statusBarNotifier);
mListeners.add(mCallLogManager);
mListeners.add(mPhoneStateBroadcaster);
mListeners.add(mInCallController);
mListeners.add(mCallAudioManager);
mListeners.add(missedCallNotifier);
mListeners.add(mHeadsetMediaButton);
mListeners.add(mProximitySensorManager);
mListeners.add(mViceNotificationImpl);
可以看出有通话记录相关的类,状态栏相关的类,耳机相关的类,距离传感器相关的类,其中重点是InCallController,看见Controller大家应该明白,InCallController是处理逻辑的关键类.
3.InCallController中处理
@Override
public void onCallAdded(Call call) {
if (!isBoundToServices()) {
bindToServices(call);
} else {
adjustServiceBindingsForEmergency();
Log.i(this, "onCallAdded: %s", call);
// Track the call if we don't already know about it.
addCall(call);
List componentsUpdated = new ArrayList<>();
for (Map.Entry entry : mInCallServices.entrySet()) {
InCallServiceInfo info = entry.getKey();
if (call.isExternalCall() && !info.isExternalCallsSupported()) {
continue;
}
componentsUpdated.add(info.getComponentName());
IInCallService inCallService = entry.getValue();
ParcelableCall parcelableCall = ParcelableCallUtils.toParcelableCall(call,
true /* includeVideoProvider */, mCallsManager.getPhoneAccountRegistrar(),
info.isExternalCallsSupported());
try {
inCallService.addCall(parcelableCall);
} catch (RemoteException ignored) {
}
}
Log.i(this, "Call added to components: %s", componentsUpdated);
}
}
该处会判断如果没有连接到服务就去绑定服务,我们看看去绑定什么服务:
public void bindToServices(Call call) {
InCallServiceConnection dialerInCall = null;
InCallServiceInfo defaultDialerComponentInfo = getDefaultDialerComponent();
Log.i(this, "defaultDialer: " + defaultDialerComponentInfo);
if (defaultDialerComponentInfo != null &&
!defaultDialerComponentInfo.getComponentName().equals(mSystemInCallComponentName)) {
dialerInCall = new InCallServiceBindingConnection(defaultDialerComponentInfo);
}
Log.i(this, "defaultDialer: " + dialerInCall);
InCallServiceInfo systemInCallInfo = getInCallServiceComponent(mSystemInCallComponentName,
IN_CALL_SERVICE_TYPE_SYSTEM_UI);
EmergencyInCallServiceConnection systemInCall =
new EmergencyInCallServiceConnection(systemInCallInfo, dialerInCall);
systemInCall.setHasEmergency(mCallsManager.hasEmergencyCall());
InCallServiceConnection carModeInCall = null;
InCallServiceInfo carModeComponentInfo = getCarModeComponent();
if (carModeComponentInfo != null &&
!carModeComponentInfo.getComponentName().equals(mSystemInCallComponentName)) {
carModeInCall = new InCallServiceBindingConnection(carModeComponentInfo);
}
mInCallServiceConnection =
new CarSwappingInCallServiceConnection(systemInCall, carModeInCall);
mInCallServiceConnection.setCarMode(shouldUseCarModeUI());
mInCallServiceConnection.connect(call);
List nonUIInCallComponents =
getInCallServiceComponents(IN_CALL_SERVICE_TYPE_NON_UI);
List nonUIInCalls = new LinkedList<>();
for (InCallServiceInfo serviceInfo : nonUIInCallComponents) {
nonUIInCalls.add(new InCallServiceBindingConnection(serviceInfo));
}
mNonUIInCallServiceConnections = new NonUIInCallServiceConnectionCollection(nonUIInCalls);
mNonUIInCallServiceConnections.connect(call);
}
该处实际会调用InCallServiceBindingConnection的connect方法
public boolean connect(Call call) {
if (mIsConnected) {
Log.event(call, Log.Events.INFO, "Already connected, ignoring request.");
return true;
}
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;
if (!mContext.bindServiceAsUser(intent, mServiceConnection,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE |
Context.BIND_ABOVE_CLIENT,
UserHandle.CURRENT)) {
Log.w(this, "Failed to connect.");
mIsConnected = false;
}
if (call != null && mIsConnected) {
call.getAnalytics().addInCallService(
mInCallServiceInfo.getComponentName().flattenToShortString(),
mInCallServiceInfo.getType());
}
return mIsConnected;
}
public static final String SERVICE_INTERFACE = "android.telecom.InCallService";
会去绑定action为android.telecom.InCallService的service,系统只有一个服务是接收该action的,就是InCallServiceImpl,路径为packages/apps/Dialer/InCallUI/src/com/android/incallui/InCallServiceImpl.java,该类继承自InCallService.
在InCallController中onCallAdded的bindToServices分析完了,绑定的是InCallServiceImpl,然后会调用inCallService.addCall(parcelableCall),那我们就知道实际走的是InCallServiceImpl的addCall,同时注意,该处已经由Telecom进程走到了Incall进程中,所以InCallServiceImpl是Telecom和Incall交互的媒介(其实有点不严谨,因为Telecom设置了android:process="system",所以运行在系统进程里面,为了更清晰,所以描述为Telecom进程).
4.InCallServiceImpl中处理,调用addCall,实际调用的是InCallServiceImpl的onCallAdded,中间饶了一下,有兴趣读者可以自己分析.
public void onCallAdded(Call call) {
InCallPresenter.getInstance().onCallAdded(call);
}
5.InCallPresenter的onCallAdded:
public void onCallAdded(final android.telecom.Call call) {
if (shouldAttemptBlocking(call)) {
maybeBlockCall(call);
} else {
if (call.getDetails()
.hasProperty(CallSdkCompat.Details.PROPERTY_IS_EXTERNAL_CALL)) {
mExternalCallList.onCallAdded(call);
} else {
mCallList.onCallAdded(call);
}
}
// Since a call has been added we are no longer waiting for Telecom to send us a call.
setBoundAndWaitingForOutgoingCall(false, null);
call.registerCallback(mCallCallback);
}
6.CallList的onCallAdded:
public void onCallAdded(final android.telecom.Call telecomCall) {
Trace.beginSection("onCallAdded");
final Call call = new Call(telecomCall);
Log.d(this, "onCallAdded: callState=" + call.getState());
if (call.getState() == Call.State.INCOMING ||
call.getState() == Call.State.CALL_WAITING) {
onIncoming(call, call.getCannedSmsResponses());
} else {
onUpdate(call);
}
call.logCallInitiationType();
Trace.endSection();
}
我们不是来电,是去电,所以走的是onUpdate
public void onUpdate(Call call) {
Trace.beginSection("onUpdate");
PhoneAccountHandle ph = call.getAccountHandle();
Log.d(this, "onUpdate - " + call + " ph:" + ph);
try {
if (call.mIsActiveSub && ph != null) {
int sub = Integer.parseInt(ph.getId());
Log.d(this, "onUpdate - sub:" + sub + " mSubId:" + mSubId);
if(sub != mSubId) {
setActiveSubId(sub);
}
}
} catch (NumberFormatException e) {
Log.w(this,"Sub Id is not a number " + e);
}
onUpdateCall(call);
notifyGenericListeners();
Trace.endSection();
}
然后调用notifyGenericListeners
private void notifyGenericListeners() {
for (Listener listener : mListeners) {
listener.onCallListChange(this);
}
}
7.InCallPresenter的onCallListChange
public void onCallListChange(CallList callList) {
if (mInCallActivity != null && mInCallActivity.getCallCardFragment() != null &&
mInCallActivity.getCallCardFragment().isAnimating()) {
mAwaitingCallListUpdate = true;
return;
}
if (callList == null) {
return;
}
mAwaitingCallListUpdate = false;
InCallState newState = getPotentialStateFromCallList(callList);
InCallState oldState = mInCallState;
Log.d(this, "onCallListChange oldState= " + oldState + " newState=" + newState);
newState = startOrFinishUi(newState);
Log.d(this, "onCallListChange newState changed to " + newState);
// Set the new state before announcing it to the world
Log.i(this, "Phone switching state: " + oldState + " -> " + newState);
mInCallState = newState;
// notify listeners of new state
for (InCallStateListener listener : mListeners) {
Log.d(this, "Notify " + listener + " of state " + mInCallState.toString());
listener.onStateChange(oldState, mInCallState, callList);
}
if (isActivityStarted()) {
final boolean hasCall = callList.getActiveOrBackgroundCall() != null ||
callList.getOutgoingCall() != null;
mInCallActivity.dismissKeyguard(hasCall);
}
if (CallList.getInstance().isDsdaEnabled() && (mInCallActivity != null)) {
mInCallActivity.updateDsdaTab();
}
}
看见了没startOrFinishUi,该句最终会拉起IncallActivity.