源码路径:packages\apps\Dialer\InCallUI
注:android7.0之前packages\apps\InCallUI
InCallUI\AndroidManifest.xml
InCallUI的组件声明在Dialer\AndroidManifest.xml
IncallActivity不能通过外部APP直接启动,只能通过广播的方式启动,或者是能全屏的通知(比如:来电)
// android:excludeFromRecents="true" 不让activity显示在Recent screens中
所以要想属性生效设置该属性的 Activity 必须是 Task 的根 Activity。如果在某个 Task 非根 Activity 中设置 android:excludeFromRecents 是没有任何效果的。
// android:screenOrientation="nosensor" 不受重力感应横竖屏切换
// android:directBootAware="true"
在此 Direct Boot Mode 下 APP 比较适合做一些:
使用场景,比如手机丢了,捡到的人解不开锁,也做不了啥操作,现在任意工作在Direct Boot Mode 下的APP都可以“安全地”跑起来,和服务器建链,更加全方位的和捡手机的人进行沟通。更多的场景还需要更大的脑洞。
APP在进入DBM 后会收到系统的广播消息: Intent.ACTION_LOCKED_BOOT_COMPLETED —— 新增的。
用户解锁手机后,APP会收到另一条: Intent.ACTION_BOOT_COMPLETED —— 一直都有
// android:resizeableActivity="true" 支持分屏任务,android7.0以下不能设置该属性
packages\apps\Dialer\InCallUI\src\com\android\incallui\InCallActivity.java
InCallActivity的根布局
// setContentView(R.layout.incall_screen);
incall_screen.xml
IncallActivity的布局相对复杂,静态布局和动态布局相结合,下文会介绍CallCardFragment。
类图说明:InCallActivity继承了TransactionSafeActivity,IncallActivity类负责UI展示,InCallPresenter是InCallActivity的代理类,负责相关的逻辑处理,通过回调与InCallActivity进行交互。
InCallActivity的 onCreate()方法中添加了亮屏、悬浮锁屏界面,过滤无关的触摸事件的Flag
// set this flag so this activity will stay in front of the keyguard
// Have the WindowManager filter out touch events that are "too fat".
int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
| WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON
| WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES;
getWindow().addFlags(flags);
InCallActivity中相关Fragment如下:
private CallButtonFragment mCallButtonFragment;
private CallCardFragment mCallCardFragment;
private AnswerFragment mAnswerFragment;
private DialpadFragment mDialpadFragment;
private ConferenceManagerFragment mConferenceManagerFragment;
...
...
// CallCardFragment
boolean showContactPhoto = !VideoCallPresenter.showIncomingVideo(videoState, state);
CallCardFragment:用于显示联系人信息及通话时间等;
CallButtonFragment:通话界面下方的控制按钮。
DialpadFragment:拨号盘显示控件。
AnswerFragment:来电控制控件,用于操作接听/拒接/短信快捷回复。
ConferenceManagerFragment:会议电话的界面。
VideoCallFragment:视屏通话控件,在CallCardFragment中调用。
private void showFragment(String tag, boolean show, boolean executeImmediately) {
Trace.beginSection("showFragment - " + tag);
final FragmentManager fm = getFragmentManagerForTag(tag);
if (fm == null) {
Log.w(TAG, "Fragment manager is null for : " + tag);
return;
}
Fragment fragment = fm.findFragmentByTag(tag);
if (!show && fragment == null) {
// Nothing to show, so bail early.
return;
}
final FragmentTransaction transaction = fm.beginTransaction();
if (show) {
if (fragment == null) {
fragment = createNewFragmentForTag(tag);
transaction.add(getContainerIdForFragment(tag), fragment, tag);
} else {
transaction.show(fragment);
}
Logger.logScreenView(getScreenTypeForTag(tag), this);
} else {
transaction.hide(fragment);
}
transaction.commitAllowingStateLoss();
if (executeImmediately) {
fm.executePendingTransactions();
}
Trace.endSection();
}
通过tag懒加载Fragment
private Fragment createNewFragmentForTag(String tag) {
if (TAG_DIALPAD_FRAGMENT.equals(tag)) {
mDialpadFragment = new DialpadFragment();
return mDialpadFragment;
} else if (TAG_ANSWER_FRAGMENT.equals(tag)) {
if (AccessibilityUtil.isTalkBackEnabled(this)) {
mAnswerFragment = new AccessibleAnswerFragment();
} else {
mAnswerFragment = new GlowPadAnswerFragment();
}
return mAnswerFragment;
} else if (TAG_CONFERENCE_FRAGMENT.equals(tag)) {
mConferenceManagerFragment = new ConferenceManagerFragment();
return mConferenceManagerFragment;
} else if (TAG_CALLCARD_FRAGMENT.equals(tag)) {
mCallCardFragment = new CallCardFragment();
return mCallCardFragment;
}
throw new IllegalStateException("Unexpected fragment: " + tag);
}
packages\apps\Dialer\InCallUI\src\com\android\incallui\CallCardFragment.java
InCallActivity中的几个fragment都继承了BaseFragment,每个Fragment都有一个Presenter(降低代码耦合性); BaseFragment、Presenter都采用了泛型设计:
Java语言引入泛型的好处是安全简单。泛型的好处是在编译的时候检查类型安全,并且所有的强制转换都是自动和隐式的,提高代码的重用率。
CallCardFragment的关系类图如下:
public abstract class BaseFragment
public abstract T createPresenter();
public abstract U getUi();
protected BaseFragment() {
//当实例一个对应的Fragment时,会初始化Fragment对的Presenter
mPresenter = createPresenter();
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//getUi()获取对应的Fragment对象,回调代理类的onUiReady方法,对fragment进行相应的初始化
mPresenter.onUiReady(getUi());
}
@Override
public void onAttach(Activity activity) {
super.onAttach(activity);
//回调InCallActivity的onFragmentAttached,初始化Fragment对象
((FragmentDisplayManager) activity).onFragmentAttached(this);
}
Presenter.java
public abstract class Presenter {
private U mUi;
/**
* Called after the UI view has been created. That is when fragment.onViewCreated() is called.
*
* @param ui The Ui implementation that is now ready to be used.
*/
public void onUiReady(U ui) {
mUi = ui;
}
/**
* Called when the UI view is destroyed in Fragment.onDestroyView().
*/
public final void onUiDestroy(U ui) {
onUiUnready(ui);
mUi = null;
}
/**
* To be overriden by Presenter implementations. Called when the fragment is being
* destroyed but before ui is set to null.
*/
public void onUiUnready(U ui) {
}
public void onSaveInstanceState(Bundle outState) {}
public void onRestoreInstanceState(Bundle savedInstanceState) {}
public U getUi() {
return mUi;
}
}
CallCardFragment.java
对应的主布局call_card_fragment.xml ------ 路径:packages\apps\Dialer\InCallUI\res\layout
...
...
...
android:dividerHeight="@dimen/contact_context_list_item_padding" />
CallCardFragment静态布局和动态布局相结合的方式,实现CallCardPresenter.CallCardUi接口,根据对应的电话状态动态加载布局,CallCardFragment负责UI显示(设置背景色,设置控件间距等),CallCardPresenter负责具体的显示逻辑处理(电话事件的监听/电话详情监听/来电监听/电话状态监听),通过回调的方式通知CallCardFragment
public class CallCardPresenter extends Presenter
implements InCallStateListener, IncomingCallListener, InCallDetailsListener,
InCallEventListener, CallList.CallUpdateListener, DistanceHelper.Listener {
...
...
@Override
public void onStateChange(InCallState oldState, InCallState newState, CallList callList) {
...
// Refresh primary call information if either:
// 1. Primary call changed.
// 2. The call's ability to manage conference has changed.
// 3. The call subject should be shown or hidden.
if (shouldRefreshPrimaryInfo(primaryChanged, ui, shouldShowCallSubject(mPrimary))) {
// primary call has changed
if (previousPrimary != null) {
//clear progess spinner (if any) related to previous primary call
maybeShowProgressSpinner(previousPrimary.getState(),
Call.SessionModificationState.NO_REQUEST);
CallList.getInstance().removeCallUpdateListener(previousPrimary.getId(), this);
}
CallList.getInstance().addCallUpdateListener(mPrimary.getId(), this);
mPrimaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(mContext, mPrimary,
isGeocoderLocationNeeded(mPrimary));
updatePrimaryDisplayInfo();
maybeStartSearch(mPrimary, true);
maybeClearSessionModificationState(mPrimary);
}
if (previousPrimary != null && mPrimary == null) {
//clear progess spinner (if any) related to previous primary call
maybeShowProgressSpinner(previousPrimary.getState(),
Call.SessionModificationState.NO_REQUEST);
CallList.getInstance().removeCallUpdateListener(previousPrimary.getId(), this);
}
if (mSecondary == null) {
// Secondary call may have ended. Update the ui.
mSecondaryContactInfo = null;
updateSecondaryDisplayInfo();
} else if (secondaryChanged) {
// secondary call has changed
mSecondaryContactInfo = ContactInfoCache.buildCacheEntryFromCall(mContext, mSecondary,
mSecondary.getState() == Call.State.INCOMING);
updateSecondaryDisplayInfo();
maybeStartSearch(mSecondary, false);
maybeClearSessionModificationState(mSecondary);
}
// Start/stop timers.
if (isPrimaryCallActive()) {
Log.d(this, "Starting the calltime timer");
mPrimary.triggerCalcBaseChronometerTime();
mCallTimer.start(CALL_TIME_UPDATE_INTERVAL_MS);
} else {
Log.d(this, "Canceling the calltime timer");
mCallTimer.cancel();
ui.setPrimaryCallElapsedTime(false, 0);
}
// Set the call state
int callState = Call.State.IDLE;
if (mPrimary != null) {
callState = mPrimary.getState();
updatePrimaryCallState();
} else {
getUi().setCallState(
callState,
VideoProfile.STATE_AUDIO_ONLY,
Call.SessionModificationState.NO_REQUEST,
new DisconnectCause(DisconnectCause.UNKNOWN),
null,
null,
null,
false /* isWifi */,
false /* isConference */,
false /* isWorkCall */);
getUi().showHdAudioIndicator(false);
}
...
}
...
@Override
public void onUiReady(CallCardUi ui) {
super.onUiReady(ui);
if (mContactsPreferences != null) {
mContactsPreferences.refreshValue(ContactsPreferences.DISPLAY_ORDER_KEY);
}
// Contact search may have completed before ui is ready.
if (mPrimaryContactInfo != null) {
updatePrimaryDisplayInfo();
}
// Register for call state changes last
//电话状态监听
//对应 public void onStateChange(InCallState oldState, InCallState newState, CallList callList);
InCallPresenter.getInstance().addListener(this);
//来电监听
//对应 public void onIncomingCall(InCallState oldState, InCallState newState, Call call);
InCallPresenter.getInstance().addIncomingCallListener(this);
//对应 public void onDetailsChanged(Call call, android.telecom.Call.Details details);
InCallPresenter.getInstance().addDetailsListener(this);
//对应
/*
/**
* Interface implemented by classes that need to know about events which occur within the
* In-Call UI. Used as a means of communicating between fragments that make up the UI.
*/
public interface InCallEventListener {
public void onFullscreenModeChanged(boolean isFullscreenMode);
public void onSecondaryCallerInfoVisibilityChanged(boolean isVisible, int height);
public void updatePrimaryCallState();
public void onIncomingVideoAvailabilityChanged(boolean isAvailable);
public void onSendStaticImageStateChanged(boolean isEnabled);
public void onAnswerViewGrab(boolean isGrabbed);
}
*/
InCallPresenter.getInstance().addInCallEventListener(this);
}
...
public interface CallCardUi extends Ui {
void setVisible(boolean on);
void setContactContextTitle(View listHeaderView);
void setContactContextContent(ListAdapter listAdapter);
void showContactContext(boolean show);
void setCallCardVisible(boolean visible);
void setPrimary(String number, String name, boolean nameIsNumber, String label,
Drawable photo, boolean isSipCall, boolean isContactPhotoShown, boolean isWorkCall);
void setSecondary(boolean show, String name, boolean nameIsNumber, String label,
String providerLabel, boolean isConference, boolean isVideoCall,
boolean isFullscreen);
void setSecondaryInfoVisible(boolean visible);
void setCallState(int state, int videoState, int sessionModificationState,
DisconnectCause disconnectCause, String connectionLabel,
Drawable connectionIcon, String gatewayNumber, boolean isWifi,
boolean isConference, boolean isWorkCall);
void setPrimaryCallElapsedTime(boolean show, long duration);
void setPrimaryName(String name, boolean nameIsNumber);
void setPrimaryImage(Drawable image, boolean isVisible);
void setPrimaryPhoneNumber(String phoneNumber);
void setPrimaryLabel(String label);
void setEndCallButtonEnabled(boolean enabled, boolean animate);
void setCallbackNumber(String number, boolean isEmergencyCalls);
void setCallSubject(String callSubject);
void setProgressSpinnerVisible(boolean visible);
void showHdAudioIndicator(boolean visible);
void showForwardIndicator(boolean visible);
void showManageConferenceCallButton(boolean visible);
boolean isManageConferenceVisible();
boolean isCallSubjectVisible();
void animateForNewOutgoingCall();
void sendAccessibilityAnnouncement();
void showNoteSentToast();
void showVbButton(boolean show);
}
}
public class CallCardFragment extends BaseFragment implements CallCardPresenter.CallCardUi {
...
...
}
InCallUI布局、代码设计做了一个简单的介绍,具体实现大家可以参考源码。
拨号盘到拉起InCallUI的流程,如类图:
具体流程分析,关键的地方有注解:
@// packages/apps/Dialer/src/com/android/dialer/dialpad/DialpadFragment.java
private void handleDialButtonPressed() {
...
// getCallIntent获取一个填充的Intent(Intent.ACTION_CALL)
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);
}
@// packages/apps/Dialer/src/com/android/dialer/util/DialerUtils.java
public static void startActivityWithErrorToast(Context context, Intent intent, int msgId) {
try {
if ((IntentUtil.CALL_ACTION.equals(intent.getAction())
&& context instanceof Activity)) {
// All dialer-initiated calls should pass the touch point to the InCallUI
Point touchPoint = TouchPointManager.getInstance().getPoint();
if (touchPoint.x != 0 || touchPoint.y != 0) {
Bundle extras;
// Make sure to not accidentally clobber any existing extras
if (intent.hasExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS)) {
extras = intent.getParcelableExtra(
TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
} else {
extras = new Bundle();
}
// touchPiont 将会传递至InCallActivity,touchPoint用于动画展示的起点位置,如果extras为空,那么将直接显示IncallUI,不会有过渡动画。
extras.putParcelable(TouchPointManager.TOUCH_POINT, touchPoint);
intent.putExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS, extras);
}
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();
}
}
@// packages\apps\Dialer\src\com\android\dialer\util\TelecomUtil.java
TelecomManagerCompat.placeCall(activity, getTelecomManager(activity), intent);
@// packages\apps\ContactsCommon\src\com\android\contacts\common\compat\telecom\TelecomManagerCompat.java
public static void placeCall(@Nullable Activity activity,
@Nullable TelecomManager telecomManager, @Nullable Intent intent) {
if (activity == null || telecomManager == null || intent == null) {
return;
}
if (CompatUtils.isMarshmallowCompatible()) {
// 版本大于等于Android6.0
telecomManager.placeCall(intent.getData(), intent.getExtras());
return;
}
activity.startActivityForResult(intent, 0);
}
@// frameworks\base\telecomm\java\android\telecom\TelecomManager.java
@RequiresPermission(android.Manifest.permission.CALL_PHONE)
public void placeCall(Uri address, Bundle extras) {
// import com.android.internal.telecom.ITelecomService; AIDL远程调用
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);
}
}
}
@// packages\services\Telecomm\src\com\android\server\telecom\TelecomServiceImpl.java
...
//远程接口绑定,实现了frameworks\base\telecomm 服务与 packages\services\Telecomm 服务之间的交互
private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub()
...
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);
}
}
@// packages\services\Telecomm\src\com\android\server\telecom\components\UserCallIntentProcessor.java
public void processIntent(Intent intent, String callingPackageName,
boolean canCallNonEmergency) {
// Ensure call intents are not processed on devices that are not capable of calling.
if (!isVoiceCapable()) {
return;
}
String action = intent.getAction();
// 通过Action限制呼叫
if (Intent.ACTION_CALL.equals(action) ||
Intent.ACTION_CALL_PRIVILEGED.equals(action) ||
Intent.ACTION_CALL_EMERGENCY.equals(action)) {
processOutgoingCallIntent(intent, callingPackageName, canCallNonEmergency);
}
}
@// 该方法中有两处对拨号进行权限检查,分别是紧急拨号限制和普通拨号限制
private void processOutgoingCallIntent(Intent intent, String callingPackageName,
boolean canCallNonEmergency) {
Uri handle = intent.getData();
String scheme = handle.getScheme();
String uriString = handle.getSchemeSpecificPart();
if (!PhoneAccount.SCHEME_VOICEMAIL.equals(scheme)) {
handle = Uri.fromParts(PhoneNumberUtils.isUriNumber(uriString) ?
PhoneAccount.SCHEME_SIP : PhoneAccount.SCHEME_TEL, uriString, null);
}
// Check DISALLOW_OUTGOING_CALLS restriction. Note: We are skipping this check in a managed
// profile user because this check can always be bypassed by copying and pasting the phone
// number into the personal dialer.
if (!UserUtil.isManagedProfile(mContext, mUserHandle)) {
// Only emergency calls are allowed for users with the DISALLOW_OUTGOING_CALLS
// restriction.
if (!TelephonyUtil.shouldProcessAsEmergency(mContext, handle)) {
final UserManager userManager = (UserManager) mContext.getSystemService(
Context.USER_SERVICE);
if (userManager.hasBaseUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
mUserHandle)) {
showErrorDialogForRestrictedOutgoingCall(mContext,
R.string.outgoing_call_not_allowed_user_restriction);
Log.w(this, "Rejecting non-emergency phone call due to DISALLOW_OUTGOING_CALLS "
+ "restriction");
return;
} else if (userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
mUserHandle)) {
RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext,
EnforcedAdmin.MULTIPLE_ENFORCED_ADMIN);
return;
}
}
}
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);
intent.putExtra(CallIntentProcessor.KEY_IS_PRIVILEGED_DIALER,
isDefaultOrSystemDialer(callingPackageName));
// Save the user handle of current user before forwarding the intent to primary user.
intent.putExtra(CallIntentProcessor.KEY_INITIATING_USER, mUserHandle);
sendBroadcastToReceiver(intent);
}
private boolean sendBroadcastToReceiver(Intent intent) {
intent.putExtra(CallIntentProcessor.KEY_IS_INCOMING_CALL, false);
intent.setFlags(Intent.FLAG_RECEIVER_FOREGROUND);
// 直接指定启动PrimaryCallReceiver
intent.setClass(mContext, PrimaryCallReceiver.class);
Log.d(this, "Sending broadcast as user to CallReceiver");
mContext.sendBroadcastAsUser(intent, UserHandle.SYSTEM);
return true;
}
@// packages\services\Telecomm\src\com\android\server\telecom\components\PrimaryCallReceiver.java
@Override
public void onReceive(Context context, Intent intent) {
Log.startSession("PCR.oR");
synchronized (getTelecomSystem().getLock()) {
getTelecomSystem().getCallIntentProcessor().processIntent(intent);
}
Log.endSession();
}
@// packages\services\Telecomm\src\com\android\server\telecom\CallIntentProcessor.java
static void processOutgoingCallIntent(
Context context,
CallsManager callsManager,
Intent intent) {
Uri handle = intent.getData();
...
// 获取Intent传递的值
...
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.
// 一个异步通话通常不会放在广播中,一旦广播接收完毕,当内存紧张时对应的广播进程将会被回收。但是这里是没有问题的,因为Telecom进程将在整个电话过程中运行,所不会被杀。[动态广播的生命依附于创建它的Context]
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);
}
}
}
@// packages\services\Telecomm\src\com\android\server\telecom\CallsManager.java
Call startOutgoingCall(Uri handle, PhoneAccountHandle phoneAccountHandle, Bundle extras,
UserHandle initiatingUser) {
...
// 创建一个Call对象,关联电话账户等...
...
// 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);
}
}
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).
// 遍历mListeners,回调每一个监听对象listener.onCallAdded(call),在CallsManager的构造方法中,有对每一个监听对象进行添加。启动IncallUI的是mInCallController对象。
/*
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);
*/
for (CallsManagerListener listener : mListeners) {
if (Log.SYSTRACE_DEBUG) {
Trace.beginSection(listener.getClass().toString() + " addCall");
}
listener.onCallAdded(call);
if (Log.SYSTRACE_DEBUG) {
Trace.endSection();
}
}
RcsCallHandler.getInstance().updateCallComposerDataToCall(call);
Trace.endSection();
}
@// packages\services\Telecomm\src\com\android\server\telecom\InCallController.java
@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就是InCallServiceImpl的对象,InCallServiceImpl继承了IInCallService
inCallService.addCall(parcelableCall);
} catch (RemoteException ignored) {
}
}
Log.i(this, "Call added to components: %s", componentsUpdated);
}
}
@// frameworks\base\telecomm\java\android\telecom\InCallService.java
@Override
public IBinder onBind(Intent intent) {
// InCallService是一个抽象类,此处返回的是其对应的子类
return new InCallServiceBinder();
}
@// packages\apps\Dialer\InCallUI\src\com\android\incallui\InCallServiceImpl.java
@Override
public IBinder onBind(Intent intent) {
final Context context = getApplicationContext();
final ContactInfoCache contactInfoCache = ContactInfoCache.getInstance(context);
InCallPresenter.getInstance().setUp(
getApplicationContext(),
CallList.getInstance(),
new ExternalCallList(),
AudioModeProvider.getInstance(),
new StatusBarNotifier(context, contactInfoCache),
new ExternalCallNotifier(context, contactInfoCache),
contactInfoCache,
new ProximitySensor(
context,
AudioModeProvider.getInstance(),
new AccelerometerListener(context))
);
InCallPresenter.getInstance().onServiceBind();
InCallPresenter.getInstance().maybeStartRevealAnimation(intent);
TelecomAdapter.getInstance().setInCallService(this);
return super.onBind(intent);
}
@// packages\apps\Dialer\InCallUI\src\com\android\incallui\InCallPresenter.java
public void maybeStartRevealAnimation(Intent intent) {
if (intent == null || mInCallActivity != null) {
return;
}
final Bundle extras = intent.getBundleExtra(TelecomManager.EXTRA_OUTGOING_CALL_EXTRAS);
if (extras == null) {
// Incoming call, just show the in-call UI directly.
return;
}
if (extras.containsKey(android.telecom.Call.AVAILABLE_PHONE_ACCOUNTS)) {
// Account selection dialog will show up so don't show the animation.
return;
}
final PhoneAccountHandle accountHandle =
intent.getParcelableExtra(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE);
final Point touchPoint = extras.getParcelable(TouchPointManager.TOUCH_POINT);
InCallPresenter.getInstance().setBoundAndWaitingForOutgoingCall(true, accountHandle);
final Intent incallIntent = getInCallIntent(false, true);
incallIntent.putExtra(TouchPointManager.TOUCH_POINT, touchPoint);
绕了这么久,终于可以启动IncallActiviy了
mContext.startActivity(incallIntent);
}
public Intent getInCallIntent(boolean showDialpad, boolean newOutgoingCall) {
final Intent intent = new Intent(Intent.ACTION_MAIN, null);
intent.setFlags(Intent.FLAG_ACTIVITY_NO_USER_ACTION | Intent.FLAG_ACTIVITY_NEW_TASK);
// 指定启动对象,结合AndroidManifest.xml中InCallActivity的注解理解一遍
intent.setClass(mContext, InCallActivity.class);
if (showDialpad) {
intent.putExtra(InCallActivity.SHOW_DIALPAD_EXTRA, true);
}
intent.putExtra(InCallActivity.NEW_OUTGOING_CALL_EXTRA, newOutgoingCall);
return intent;
}
InCallUI的启动流程分析完了,由于知识水平有限,本文难免有写的不对的地方,还望大家指正。