描述:當手機已經在通話中的時候,界面回到桌面,再打開Dialer app的時候,手機就會提示並讓你確認到底你是想打開已經在通話的界面還是不管正在通話中的電話而重新撥打新的通話。
假如此刻你想打開正在通話的界面,android的源碼不是直接在DialtactsActivity
裏startActivity
,而是經過系統通話服務最終啓動。
這個過程裏,代碼從Dialer
走到Framework
,然後從Framework
走到Telecom
,再從Telecom
走到Framework
,再從Framework
走到Dialer
。跨進程通信的過程比較值得回味。
當DialtactsActivity啓動後,會去檢查是否正在通話中,
如果在通話中,就會讓Dialpadframgent
顯示一個Listview
形式的DialpadChooser
,當點擊回到通話界面的選項時,會會回調到onItemClick
@Override
public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
DialpadChooserAdapter.ChoiceItem item =
(DialpadChooserAdapter.ChoiceItem) parent.getItemAtPosition(position);
int itemId = item.id;
if (itemId == DialpadChooserAdapter.DIALPAD_CHOICE_USE_DTMF_DIALPAD) {
returnToInCallScreen(true);
} else if (itemId == DialpadChooserAdapter.DIALPAD_CHOICE_RETURN_TO_CALL) {
returnToInCallScreen(false);
} else if (itemId == DialpadChooserAdapter.DIALPAD_CHOICE_ADD_NEW_CALL) {
showDialpadChooser(false);
} else {
LogUtil.w("DialpadFragment.onItemClick", "Unexpected itemId: " + itemId);
}
}
第二項DIALPAD_CHOICE_RETURN_TO_CALL
returnToInCallScreen(false)
這裏銷燬DialtactsActivity
並調用TelecomUtil
去處理顯示InCallScreen
;
private void returnToInCallScreen(boolean showDialpad) {
TelecomUtil.showInCallScreen(getActivity(), showDialpad);
getActivity().finish();
}
TelecomUtil
:
public static void showInCallScreen(Context context, boolean showDialpad) {
if (hasReadPhoneStatePermission(context)) {
try {
getTelecomManager(context).showInCallScreen(showDialpad);
} catch (SecurityException e) {
// Just in case
LogUtil.w(TAG, "TelecomManager.showInCallScreen called without permission.");
}
}
}
getTelecomManager
獲取TelecomManager
private static TelecomManager getTelecomManager(Context context) {
return (TelecomManager) context.getSystemService(Context.TELECOM_SERVICE);
}
TelecomManager
:
public void showInCallScreen(boolean showDialpad) {
ITelecomService service = getTelecomService();
if (service != null) {
try {
service.showInCallScreen(showDialpad, mContext.getOpPackageName());
} catch (RemoteException e) {
Log.e(TAG, "Error calling ITelecomService#showCallScreen", e);
}
}
}
TelecomManager
是屬於Framework
\frameworks\base\telecomm\java\android\telecom\TelecomManager.java
getTelecomService()
private ITelecomService getTelecomService() {
if (mTelecomServiceOverride != null) {
return mTelecomServiceOverride;
}
return ITelecomService.Stub.asInterface(ServiceManager.getService(Context.TELECOM_SERVICE));
}
要找到實現ITelecomService
的實體,
發現是在TelecomServiceImpl
裏,
packages\services\Telecomm\src\com\android\server\telecom\TelecomServiceImpl.java
private final ITelecomService.Stub mBinderImpl = new ITelecomService.Stub()
內部類ITelecomService.Stub mBinderImpl
是ITelecomService
的實體,實現了ITelecomService
接口方法。
@Override
public void showInCallScreen(boolean showDialpad, String callingPackage) {
try {
Log.startSession("TSI.sICS");
if (!canReadPhoneState(callingPackage, "showInCallScreen")) {
return;
}
synchronized (mLock) {
long token = Binder.clearCallingIdentity();
try {
mCallsManager.getInCallController().bringToForeground(showDialpad);
} finally {
Binder.restoreCallingIdentity(token);
}
}
} finally {
Log.endSession();
}
}
檢查token過後就用CallsManager
獲取InCallController
,調用bringToForeground
InCallController getInCallController() {
return mInCallController;
}
這個InCallController
是在CallsManager
構造方法裏新建的
mInCallController = new InCallController(
context, mLock, this, systemStateProvider, defaultDialerCache, mTimeoutsAdapter,
emergencyCallHelper);
InCallController
:
void bringToForeground(boolean showDialpad) {
if (!mInCallServices.isEmpty()) {
for (IInCallService inCallService : mInCallServices.values()) {
try {
inCallService.bringToForeground(showDialpad);
} catch (RemoteException ignored) {
}
}
} else {
Log.w(this, "Asking to bring unbound in-call UI to foreground.");
}
}
然後就是調用IInCallService
這個遠程接口來處理,
誰實現了這個遠程接口呢?
答案在IncallService
的內部類InCallServiceBinder
裏。
InCallService
是在Framewrok
裏的,
\frameworks\base\telecomm\java\android\telecom\InCallService.java
InCallServiceBinder extends IInCallService.Stub
---
@Override
public void bringToForeground(boolean showDialpad) {
mHandler.obtainMessage(MSG_BRING_TO_FOREGROUND, showDialpad ? 1 : 0, 0).sendToTarget();
}
通過Handler
來發送message,
case MSG_BRING_TO_FOREGROUND:
mPhone.internalBringToForeground(msg.arg1 == 1);
break;
然後這裏的mPhone
對象是new出來的。
mPhone = new Phone(new InCallAdapter((IInCallAdapter) msg.obj), callingPackage,
getApplicationContext().getApplicationInfo().targetSdkVersion);
mPhone.addListener(mPhoneListener);
同時,mPhone還設置了監聽器,
mPhoneListener
也是一個內部類對象,
前面internalBringToForeground
final void internalBringToForeground(boolean showDialpad) {
fireBringToForeground(showDialpad);
}
---------
private void fireBringToForeground(boolean showDialpad) {
for (Listener listener : mListeners) {
listener.onBringToForeground(this, showDialpad);
}
}
就是去通知所有監聽器調用onBringToForeground
。
也就是InCallService
裏的Phone.Listener
@Override
public void onBringToForeground(Phone phone, boolean showDialpad) {
InCallService.this.onBringToForeground(showDialpad);
}
可是InCallService
是個抽象父類
public void onBringToForeground(boolean showDialpad) {
}
於是需要看子類實現。
InCallServiceImpl extends InCallService
packages\apps\Dialer\java\com\android\incallui\InCallServiceImpl.java
這裏InCallServiceImpl
是Dialer的UI部分,到了這一步是回到了Dialer包裏,
@Override
public void onBringToForeground(boolean showDialpad) {
InCallPresenter.getInstance().onBringToForeground(showDialpad);
}
然後就回到了InCallPresenter
。
前面講過,InCallPresenter
裏檢測狀態後才調用了showInCall
打開IncallUI。
public void showInCall(boolean showDialpad, boolean newOutgoingCall) {
mContext.startActivity(
InCallActivity.getIntent(
mContext, showDialpad, newOutgoingCall, false /* forFullScreen */));
}
這樣整個流程結束。
在這個流程裏看到了很多設計模式,
比如
TelecomManager
只是TelecomService
的代理。真實的服務又是TelecomServiceImpl
。代理模式很明顯。
Phone
保存了List<Listener>
,再用for循環通知所有Listener
,觀察者模式很明顯。
然後就是跨進程通信了,有ITelecomService
IInCallService
這兩個AIDL接口的使用。