Telephony基础之VoiceCall业务(MO流程启动InCallUI)
MO流程之启动InCallUI时序图
一,MO流程之Dialer部分
首先点击拨号盘按钮处理
DialpadFragment.java
@Override
public void onClick(View view) {
int resId = view.getId();
if (resId == R.id.dialpad_floating_action_button) {
if (isConfigAvailableNetwork) {
dialAfterNetworkCheck();
} else {
view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
handleDialButtonPressed();
}
...
}
private void handleDialButtonPressed() {
if (isDigitsEmpty() && (mRecipients == null || !mRecipients.isShown())) {
// No number entered.
handleDialButtonClickWithEmptyDigits();
} else {
...
} else {
final Intent intent = CallUtil.getCallIntent(number);
if (!isDigitsShown) {
// must be dial conference add extra
intent.putExtra(EXTRA_DIAL_CONFERENCE_URI, true);
}
intent.putExtra(ADD_PARTICIPANT_KEY, mAddParticipant && isPhoneInUse());
DialerUtils.startActivityWithErrorToast(getActivity(), intent);
hideAndClearDialpad(false);
}
}
}
}
进入DialerUtils.startActivityWithErrorToast():
public static void startActivityWithErrorToast(Context context, Intent intent, int msgId) {
try {
if ((IntentUtil.CALL_ACTION.equals(intent.getAction())
&& context instanceof Activity)) {
....
final boolean hasCallPermission = TelecomUtil.placeCall((Activity) 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);
}
} else {
context.startActivity(intent);
}
} catch (ActivityNotFoundException e) {
Toast.makeText(context, msgId, Toast.LENGTH_SHORT).show();
}
}
进入TelecomUtil.placeCall():
public static boolean placeCall(Activity activity, Intent intent) {
if (hasCallPhonePermission(activity)) {
TelecomManagerCompat.placeCall(activity, getTelecomManager(activity), intent);
return true;
}
return false;
}
进入TelecomManagerCompat.placeCall():
public static void placeCall(@Nullable Activity activity,
@Nullable TelecomManager telecomManager, @Nullable Intent intent) {
if (activity == null || telecomManager == null || intent == null) {
return;
}
if (CompatUtils.isMarshmallowCompatible()) {
telecomManager.placeCall(intent.getData(), intent.getExtras());
return;
}
activity.startActivityForResult(intent, 0);
}
进入TelecomManager.placeCall():
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);
}
}
}
二,MO流程之Telecom部分
进入TelecomServiceImpl.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, hasCallAppOp && hasCallPermission);
} finally {
Binder.restoreCallingIdentity(token);
}
}
} finally {
Log.endSession();
}
}
进入UserCallIntentProcessor.processIntent()
public void processIntent(Intent intent, String callingPackageName,
boolean canCallNonEmergency) {
...
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;
}
在PrimaryCallReceiver.onReceive()处理:
@Override
public void onReceive(Context context, Intent intent) {
Log.startSession("PCR.oR");
synchronized (getTelecomSystem().getLock()) {
...
getTelecomSystem().getCallIntentProcessor().processIntent(intent);
}
Log.endSession();
}
进入CallIntentProcessor.processIntent():
public void processIntent(Intent intent) {
final boolean isUnknownCall = intent.getBooleanExtra(KEY_IS_UNKNOWN_CALL, false);
if (isUnknownCall) {
processUnknownCallIntent(mCallsManager, intent);
} else {
processOutgoingCallIntent(mContext, mCallsManager, intent);
}
Trace.endSection();
}
进入CallIntentProcessor.processOutgoingCallIntent():
在该方法中取出了intent中携带的各个参数传入CallsManager.startOutgoingCall()用于创建Call对象,
CallsManager.startOutgoingCall()创建Telecom Call对象以此向上构建Telecom Framework Call和InCallUI Call对象,进而去启动InCallUI。
返回后,继续在processOutgoingCallIntent()中调broadcaster.processIntent()…–>CallsManager.placeOutgoingCall()向下进行拨号流程。
此处,我们只分析向上启动InCAllUI的流程。
static void processOutgoingCallIntent(
Context context,
CallsManager callsManager,
Intent intent) {
...
// Send to CallsManager to ensure the InCallUI gets kicked off before the broadcast returns
Call call = callsManager
.startOutgoingCall(handle, phoneAccountHandle, clientExtras, initiatingUser);
if (call != null) {
// Asynchronous calls should not usually be made inside a BroadcastReceiver because once
// onReceive is complete, the BroadcastReceiver's process runs the risk of getting
// killed if memory is scarce. However, this is OK here because the entire Telecom
// process will be running throughout the duration of the phone call and should never
// be killed.
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) {
callsManager.clearPendingMOEmergencyCall();
disconnectCallAndShowErrorDialog(context, call, result);
}
}
}
进入CallsManager.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 */
);
call.initAnalytics();
......
addCall(call);
}
进入addCall()中:
private void addCall(Call call) {
....
for (CallsManagerListener listener : mListeners) {
if (Log.SYSTRACE_DEBUG) {
Trace.beginSection(listener.getClass().toString() + " addCall");
}
listener.onCallAdded(call);
if (Log.SYSTRACE_DEBUG) {
Trace.endSection();
}
}
}
这里通过分发Listener进入InCallController.onCallAdded():
public void onCallAdded(Call call) {
if (!isBoundToServices()) {
bindToServices(call);
} else {
...
try {
inCallService.addCall(parcelableCall);
} catch (RemoteException ignored) {
}
}
Log.i(this, "Call added to components: %s", componentsUpdated);
}
}
总体来说,这里就会去在InCallController中通过跨进程的绑定InCallUI中的InCallServiceImpl来把Call对象给到InCallUI用来启动InCallActivity。
public void bindToServices(Call call) {
....
// [HTC_PHONE] s Zoey if VzwInCallServiceImpl is enable, not show InCallUI
if (shouldShowInCallUI()) {
mInCallServiceConnection.connect(call);
}
// [HTC_PHONE] e Zoey if VzwInCallServiceImpl is enable, not show InCallUI
....
}
这个方法封装的比较复杂,但最终都会通过mInCallServiceConnection.connect(call)调到InCallServiceBindingConnection.connect(),在此处真正进行对InCallService(实际是InCallUI中的InCallServiceImpl)进行bind:
@Override
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,
UserHandle.CURRENT)) {
Log.w(this, "Failed to connect.");
mIsConnected = false;
}
....
}
这里的Intent intent = new Intent(InCallService.SERVICE_INTERFACE);其中Intent.Action==SERVICE_INTERFACE = “android.telecom.InCallService”;其指向的就是InCallUI中的InCallServiceImpl extends InCallService:
<service android:name="com.android.incallui.InCallServiceImpl"
android:permission="android.permission.BIND_INCALL_SERVICE"
android:directBootAware="true" >
<meta-data android:name="android.telecom.IN_CALL_SERVICE_UI" android:value="true" />
<meta-data android:name="android.telecom.INCLUDE_EXTERNAL_CALLS" android:value="true" />
<intent-filter>
<action android:name="android.telecom.InCallService"/>
</intent-filter>
</service>
在绑定service以后会返回InCallServiceBinder对象:
@Override
public IBinder onBind(Intent intent) {
return new InCallServiceBinder();
}
然后进入 ServiceConnection.onServiceConnected():
private final ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.startSession("ICSBC.oSC");
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();
}
}
}
这onConnected()方法逐步调到InCallController.onConnected():
private boolean onConnected(InCallServiceInfo info, IBinder service) {
Trace.beginSection("onConnected: " + info.getComponentName());
Log.i(this, "onConnected to %s", info.getComponentName());
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) {
try {
if (call.isExternalCall() && !info.isExternalCallsSupported()) {
continue;
}
// Track the call if we don't already know about it.
addCall(call);
numCallsSent += 1;
inCallService.addCall(ParcelableCallUtils.toParcelableCall(
call,
true /* includeVideoProvider */,
mCallsManager.getPhoneAccountRegistrar(),
info.isExternalCallsSupported()));
} catch (RemoteException ignored) {
}
}
....
}
这里非常关键,通过IInCallService inCallService = IInCallService.Stub.asInterface(service);在InCallController中获得可操作InCallUI中InCallServiceImpl对象的引用inCallService。然后调用了关键的inCallService.setInCallAdapter(),通过这个方法将Telecom service中的InCallAdapter对象设到了Phone的实例中。接着远程调用inCallService.addCall(ParcelableCallUtils.toParcelableCall(call)将Telecom Service call序列化后传给InCallUI用于构建Telecom Framework Call:
三,MO流程之InCallUI部分
进入InCallService.addCall():
@Override
public void addCall(ParcelableCall call) {
mHandler.obtainMessage(MSG_ADD_CALL, call).sendToTarget();
}
case MSG_ADD_CALL:
mPhone.internalAddCall((ParcelableCall) msg.obj);
break;
final void internalAddCall(ParcelableCall parcelableCall) {
Call call = new Call(this, parcelableCall.getId(), mInCallAdapter,
parcelableCall.getState());
mCallByTelecomCallId.put(parcelableCall.getId(), call);
mCalls.add(call);
checkCallTree(parcelableCall);
call.internalUpdate(parcelableCall, mCallByTelecomCallId);
fireCallAdded(call);
}
注意这里Telecom Framework Call的构造方法有传入InCallAdapter对象,这是后面会用到的用于从上到下进行通话控制的。之后调用了fireCallAdded(call):
private void fireCallAdded(Call call) {
for (Listener listener : mListeners) {
listener.onCallAdded(this, call);
}
}
这里的listener是在前面构建Phone对象时传入的mPhoneListener:
Phone.Listener.onCallAdded():
/** ${inheritDoc} */
@Override
public void onCallAdded(Phone phone, Call call) {
InCallService.this.onCallAdded(call);
}
这里的InCallService.this实际就是InCallServiceImpl实例:
@Override
public void onCallAdded(Call call) {
InCallPresenter.getInstance().onCallAdded(call);
}
进入InCallPresenter.getInstance().onCallAdded():
public void onCallAdded(final android.telecom.Call call) {
/*if (shouldAttemptBlocking(call)) {
// maybeBlockCall(call); // not follow Google design
} else {*/
if (call.getDetails()
.hasProperty(android.telecom.Call.Details.PROPERTY_IS_EXTERNAL_CALL)) {
mExternalCallList.onCallAdded(call);
} else {
mCallList.onCallAdded(call);
}
....
}
进入CallList.onCallAdded():
public void onCallAdded(final android.telecom.Call telecommCall) {
Trace.beginSection("onCallAdded");
final Call call = new Call(telecommCall);
....
if (call.getState() == Call.State.INCOMING ||
call.getState() == Call.State.CALL_WAITING) {
onIncoming(call, call.getCannedSmsResponses());
} else {
updateMuteStateForSRVCC(call);
onUpdate(call);
....
}
此处根据Telecom Framework Call创建出对应的InCallUI Call对象,同时设置了监听。之后因为Call.state=DIALING调用onUpdate(call):
public void onUpdate(Call call) {
Trace.beginSection("onUpdate");
onUpdateCall(call);
notifyGenericListeners();
Trace.endSection();
}
public void notifyGenericListeners() {
for (Listener listener : mListeners) {
listener.onCallListChange(this);
}
}
再回到InCallPresenter.onCallListChange():
@Override
public void onCallListChange(CallList callList) {
....
InCallState newState = getPotentialStateFromCallList(callList);
InCallState oldState = mInCallState;
Log.d(this, "onCallListChange oldState= " + oldState + " newState=" + newState);
newState = startOrFinishUi(newState);
...
}
终于看到曙光了,这里的startOrFinishUi(newState)就是去启动InCallUI通话界面InCallActivity了,传入的newState是InCallState对象,该对象专门用来表征通话界面状态的。
private InCallState startOrFinishUi(InCallState newState) {
Log.d(this, "startOrFinishUi: " + mInCallState + " -> " + newState);
....
if (showCallUi || showAccountPicker) {
if(HtcLiteStateController.getInstance().donotShowUIIfLiteConnected() == false) {
Log.i(this, "Start in call UI");
showInCall(false /* showDialpad */, !showAccountPicker /* newOutgoingCall */);
}
}
....
public void showInCall(final boolean showDialpad, final boolean newOutgoingCall, boolean pendingAnswer) {
Log.i(this, "Showing InCallActivity");
Intent intent = getInCallIntent(showDialpad, newOutgoingCall);
//+[HTC_PHONE]Need to show dialog for user to select video type
intent.putExtra(EXTRA_SHOW_ANSWER_SELECT_DIALOG, pendingAnswer);
//+[HTC_PHONE]Need to show dialog for user to select video type
mContext.startActivity(intent);
}
OK,InCallActivity终于启动起来了,至于启动起来的界面是什么样的,在之后的关于InCallUI布局一节再来分析。
这里再来说几点零碎的认识:
1,在这个流程,即MO startInCallUI流程中,涉及了Dialer,Telecom Service,Telecom Framework,InCallUI四部分,Dialer不谈,只谈后面三部分。剩下的三个部分从进程角度来讲是运行在两个进程里:1.system_server 2.InCallUI 。其中Telecom Service是运行在system_server中的(android:process=”system”)。而Telecom Framework中的主要java 类是抽象出来放在框架层里,以提供给InCallUI实现或调用的,如InCallService.java,Phone.java,Call.java其并没有运行在一个单独的进程,而是在InCallUI进程中来使用。
2,Telecom service与InCallUI是通过AIDL实现了跨进程的双向互通的。具体实现的类文件是:InCallController.java InCallAdapter.java(Telecom service); InCallService.java InCallAdapter.java(Telecom Framework); InCallServiceImpl.java(InCallUI)。其中Telecom Service通过绑定service–>InCallServiceImpl获得InCallServiceBinder的binder对象用以操作InCallUI。同时调用前面说到的inCallService.setInCallAdapter()传给InCallUI一个在Telecom service中实现了AIDL接口的InCallAdapter extends IInCallAdapter.Stub对象,这就使得InCallUI中持有了操作Telecom service的接口。后面需要分析的HOLD Call, END Call等就需用到这个接口。简单流程如下:
InCallUI Call–>Telecom Framework Call–>Telecom Framwork InCallAdapter–>Telecom Service InCallAdapter