Telephony框架分析
引子
無論手機如何改變,功能如何豐富,它最核心最關鍵的功能依舊是通訊,通訊按現在的情況來說主要就是打電話發簡訊和上網,Android的通訊框架從上往下可以分為4個部分:
- Modem 這是整個通訊的硬體基礎,需要Modem晶片,不同制式需要採用不同的Modem;
- RIL 為了適配不同的Modem晶片而抽象出來的中間層,用於將Modem指令轉換為Java可用的資料流;
- Telephony 這是在Framework層搭建的通訊框架,面向開發者提供操作通訊事務的能力;
- Application 這是最上層的應用,直接面向用戶,提供撥號、上網、發簡訊的介面;
這篇文章主要對Telephony框架做一個小小的概述與總結
Telephony程序與實體
程序
整個Framework層的Telephony框架執行在一個叫做Phone(com.android.phone)的程序中。而這個程序是在packages\services\Telephony模組中被建立的(Android 8.0.0平臺)。並且該模組在AndroidManifest.xml中有如下關鍵的宣告:
<application android:name="PhoneApp"
android:persistent="true"
android:label="@string/phoneAppLabel"
android:icon="@mipmap/ic_launcher_phone"
android:allowBackup="false"
android:supportsRtl="true"
android:usesCleartextTraffic="true"
android:defaultToDeviceProtectedStorage="true"
android:directBootAware="true"
//從 Android N 開始,在首次開機時,在使用者尚未來得及解鎖裝置之前,裝置可直接啟動到一種名為 Direct Boot(直接啟動)的新模式中。在此模式下,作業系統可以全功能執行,但不允許訪問私有應用資料,只能執行經過更新、可支援直接啟動功能的應用。
//該屬性使得使用者在加密狀態(未解鎖)下能夠正常使用一些手機功能,如鬧鐘,接電話等
>
這個宣告建立了一個名叫“PhoneApp”的application,並且確定了他的name、label、icon等資訊,而且將該application的persistent屬性置為true。那麼這個persistent屬性的作用是什麼呢?這裡的persistent屬性具備兩個關鍵作用:
+ 該模組會在開機時被系統自動初始化;
+ 該模組所在的程序(com.android.phone)由於任何原因被kill掉之後,都會自動重啟(這種情況只針對系統內建app,第三方安裝的app不會被重啟);
以上兩點是十分必要的,他保證/導致了兩個效果:
- 所有Application層和Framework層中與Telephony相關的操作,包括各種Service的建立、與RIL層互動的RILJ的初始化等,都是通過Phone程序建立的;
- Phone程序由於任何原因被kill掉後,都會發生重新搜網的動作;
實體物件
前面介紹了Telephony的框架和程序,那麼當發生具體的某個通訊請求,比如打電話、發簡訊時,該如何操作呢?
這裡要引入一個非常重要的物件:Phone物件。該物件可以看做是Telephony框架的實體,可以向該物件發起各種通訊相關的請求,可以說,Phone物件是Telephony整個框架的核心,他負責與RIL層的互動。
而這個Phone物件就是在PhoneApp這個application初始化過程中被建立的。我們就從入口開始,來檢視Phone程序的建立過程:
在PhoneApp的onCreate()方法中,會new出PhoneGlobals的物件,並接著呼叫該物件的onCreate方法:
public void onCreate() {
if (UserHandle.myUserId() == 0) {
// We are running as the primary user, so should bring up the
// global phone state.
mPhoneGlobals = new PhoneGlobals(this);
mPhoneGlobals.onCreate();
mTelephonyGlobals = new TelephonyGlobals(this);
mTelephonyGlobals.onCreate();
}
}
先看PhoneGlobals的onCreate過程:
public void onCreate() {
if (VDBG) Log.v(LOG_TAG, "onCreate()...");
ContentResolver resolver = getContentResolver();
// Cache the "voice capable" flag.
// This flag currently comes from a resource (which is
// overrideable on a per-product basis):
sVoiceCapable =
getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);
// ...but this might eventually become a PackageManager "system
// feature" instead, in which case we'd do something like:
// sVoiceCapable =
// getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_VOICE_CALLS);
if (mCM == null) {
// Initialize the telephony framework
// 建立Phone物件
PhoneFactory.makeDefaultPhones(this);
// Start TelephonyDebugService After the default phone is created.
Intent intent = new Intent(this, TelephonyDebugService.class);
startService(intent);
// 初始化CallManager
mCM = CallManager.getInstance();
for (Phone phone : PhoneFactory.getPhones()) {
mCM.registerPhone(phone);
}
// Create the NotificationMgr singleton, which is used to display
// status bar icons and control other status bar behavior.
// 初始化NotificationMgr,用於狀態列通知
notificationMgr = NotificationMgr.init(this);
// If PhoneGlobals has crashed and is being restarted, then restart.
mHandler.sendEmptyMessage(EVENT_RESTART_SIP);
// Create an instance of CdmaPhoneCallState and initialize it to IDLE
cdmaPhoneCallState = new CdmaPhoneCallState();
cdmaPhoneCallState.CdmaPhoneCallStateInit();
// before registering for phone state changes
mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, LOG_TAG);
// lock used to keep the processor awake, when we don't care for the display.
mPartialWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK
| PowerManager.ON_AFTER_RELEASE, LOG_TAG);
mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
// Get UpdateLock to suppress system-update related events (e.g. dialog show-up)
// during phone calls.
mUpdateLock = new UpdateLock("phone");
if (DBG) Log.d(LOG_TAG, "onCreate: mUpdateLock: " + mUpdateLock);
CallLogger callLogger = new CallLogger(this, new CallLogAsync());
callGatewayManager = CallGatewayManager.getInstance();
// Create the CallController singleton, which is the interface
// to the telephony layer for user-initiated telephony functionality
// (like making outgoing calls.)
// 初始化CallController
callController = CallController.init(this, callLogger, callGatewayManager);
// Create the CallerInfoCache singleton, which remembers custom ring tone and
// send-to-voicemail settings.
//
// The asynchronous caching will start just after this call.
callerInfoCache = CallerInfoCache.init(this);
// 初始化PhoneInterfaceManager
phoneMgr = PhoneInterfaceManager.init(this, PhoneFactory.getDefaultPhone());
configLoader = CarrierConfigLoader.init(this);
// Create the CallNotifer singleton, which handles
// asynchronous events from the telephony layer (like
// launching the incoming-call UI when an incoming call comes
// in.)
// 初始化CallNotifer, 響鈴等動作在這裡面完成
notifier = CallNotifier.init(this);
PhoneUtils.registerIccStatus(mHandler, EVENT_SIM_NETWORK_LOCKED);
// register for MMI/USSD
mCM.registerForMmiComplete(mHandler, MMI_COMPLETE, null);
// register connection tracking to PhoneUtils
PhoneUtils.initializeConnectionHandler(mCM);
// Register for misc other intent broadcasts.
IntentFilter intentFilter =
new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
intentFilter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
intentFilter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
intentFilter.addAction(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED);
intentFilter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
intentFilter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
registerReceiver(mReceiver, intentFilter);
mCarrierVvmPackageInstalledReceiver.register(this);
//set the default values for the preferences in the phone.
PreferenceManager.setDefaultValues(this, R.xml.network_setting_fragment, false);
PreferenceManager.setDefaultValues(this, R.xml.call_feature_setting, false);
// Make sure the audio mode (along with some
// audio-mode-related state of our own) is initialized
// correctly, given the current state of the phone.
PhoneUtils.setAudioMode(mCM);
}
// XXX pre-load the SimProvider so that it's ready
resolver.getType(Uri.parse("content://icc/adn"));
// TODO: Register for Cdma Information Records
// phone.registerCdmaInformationRecord(mHandler, EVENT_UNSOL_CDMA_INFO_RECORD, null);
// Read HAC settings and configure audio hardware
if (getResources().getBoolean(R.bool.hac_enabled)) {
int hac = android.provider.Settings.System.getInt(
getContentResolver(),
android.provider.Settings.System.HEARING_AID,
0);
AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
audioManager.setParameter(SettingsConstants.HAC_KEY,
hac == SettingsConstants.HAC_ENABLED
? SettingsConstants.HAC_VAL_ON : SettingsConstants.HAC_VAL_OFF);
}
}
從以上程式碼可以看出,PhoneGlobals的初始化過程中要先通過PhoneFactory的makeDefaultPhones()方法建立Phone物件,接著完成了一系列與Telephony相關的重要服務的初始化,比如CallManager、NotificationMgr、CallCommandService、PhoneInterfaceManager、CallNotifier等。
我們現在只關心Phone物件的建立過程,也就是PhoneFactory的makeDefaultPhones過程:
public static void makeDefaultPhones(Context context) {
makeDefaultPhone(context);
}
/**
* FIXME replace this with some other way of making these
* instances
*/
public static void makeDefaultPhone(Context context) {
synchronized (sLockProxyPhones) {
if (!sMadeDefaults) {
sContext = context;
// create the telephony device controller.
TelephonyDevController.create();
int retryCount = 0;
for(;;) {
boolean hasException = false;
retryCount ++;
try {
// use UNIX domain socket to
// prevent subsequent initialization
new LocalServerSocket("com.android.internal.telephony");
} catch (java.io.IOException ex) {
hasException = true;
}
if ( !hasException ) {
break;
} else if (retryCount > SOCKET_OPEN_MAX_RETRY) {
throw new RuntimeException("PhoneFactory probably already running");
} else {
try {
Thread.sleep(SOCKET_OPEN_RETRY_MILLIS);
} catch (InterruptedException er) {
}
}
}
// 建立DefaultPhoneNotifier,負責通知Phone的狀態
sPhoneNotifier = new DefaultPhoneNotifier();
int cdmaSubscription = CdmaSubscriptionSourceManager.getDefault(context);
Rlog.i(LOG_TAG, "Cdma Subscription set to " + cdmaSubscription);
/* In case of multi SIM mode two instances of Phone, RIL are created,
where as in single SIM mode only instance. isMultiSimEnabled() function checks
whether it is single SIM or multi SIM mode */
int numPhones = TelephonyManager.getDefault().getPhoneCount();
// Start ImsResolver and bind to ImsServices.
String defaultImsPackage = sContext.getResources().getString(
com.android.internal.R.string.config_ims_package);
Rlog.i(LOG_TAG, "ImsResolver: defaultImsPackage: " + defaultImsPackage);
sImsResolver = new ImsResolver(sContext, defaultImsPackage, numPhones);
sImsResolver.populateCacheAndStartBind();
// 獲取當前網路型別
int[] networkModes = new int[numPhones];
sPhones = new Phone[numPhones];
// 根據網路型別建立RILJ,負責Framework與RIL層的互動
sCommandsInterfaces = new RIL[numPhones];
sTelephonyNetworkFactories = new TelephonyNetworkFactory[numPhones];
for (int i = 0; i < numPhones; i++) {
// reads the system properties and makes commandsinterface
// Get preferred network type.
networkModes[i] = RILConstants.PREFERRED_NETWORK_MODE;
Rlog.i(LOG_TAG, "Network Mode set to " + Integer.toString(networkModes[i]));
sCommandsInterfaces[i] = new RIL(context, networkModes[i],
cdmaSubscription, i);
}
Rlog.i(LOG_TAG, "Creating SubscriptionController");
SubscriptionController.init(context, sCommandsInterfaces);
// Instantiate UiccController so that all other classes can just
// call getInstance()
// 建立UiccController,間接建立UiccCard、UiccCardApplication、IccFileHandler、IccRecords、CatService服務
// (手機中使用的卡SIM,USIM,UIM等統稱為:UICC)
sUiccController = UiccController.make(context, sCommandsInterfaces);
// 根據當前Phone型別建立不同的PhoneProxy
for (int i = 0; i < numPhones; i++) {
Phone phone = null;
int phoneType = TelephonyManager.getPhoneType(networkModes[i]);
if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
phone = new GsmCdmaPhone(context,
sCommandsInterfaces[i], sPhoneNotifier, i,
PhoneConstants.PHONE_TYPE_GSM,
TelephonyComponentFactory.getInstance());
} else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
phone = new GsmCdmaPhone(context,
sCommandsInterfaces[i], sPhoneNotifier, i,
PhoneConstants.PHONE_TYPE_CDMA_LTE,
TelephonyComponentFactory.getInstance());
}
Rlog.i(LOG_TAG, "Creating Phone with type = " + phoneType + " sub = " + i);
sPhones[i] = phone;
}
// Set the default phone in base class.
// FIXME: This is a first best guess at what the defaults will be. It
// FIXME: needs to be done in a more controlled manner in the future.
sPhone = sPhones[0];
sCommandsInterface = sCommandsInterfaces[0];
// Ensure that we have a default SMS app. Requesting the app with
// updateIfNeeded set to true is enough to configure a default SMS app.
ComponentName componentName =
SmsApplication.getDefaultSmsApplication(context, true /* updateIfNeeded */);
String packageName = "NONE";
if (componentName != null) {
packageName = componentName.getPackageName();
}
Rlog.i(LOG_TAG, "defaultSmsApplication: " + packageName);
// Set up monitor to watch for changes to SMS packages
SmsApplication.initSmsPackageMonitor(context);
sMadeDefaults = true;
Rlog.i(LOG_TAG, "Creating SubInfoRecordUpdater ");
sSubInfoRecordUpdater = new SubscriptionInfoUpdater(context,
sPhones, sCommandsInterfaces);
SubscriptionController.getInstance().updatePhonesAvailability(sPhones);
// Start monitoring after defaults have been made.
// Default phone must be ready before ImsPhone is created because ImsService might
// need it when it is being opened. This should initialize multiple ImsPhones for
// ImsResolver implementations of ImsService.
for (int i = 0; i < numPhones; i++) {
sPhones[i].startMonitoringImsService();
}
ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(
ServiceManager.getService("telephony.registry"));
SubscriptionController sc = SubscriptionController.getInstance();
sSubscriptionMonitor = new SubscriptionMonitor(tr, sContext, sc, numPhones);
sPhoneSwitcher = new PhoneSwitcher(MAX_ACTIVE_PHONES, numPhones,
sContext, sc, Looper.myLooper(), tr, sCommandsInterfaces,
sPhones);
sProxyController = ProxyController.getInstance(context, sPhones,
sUiccController, sCommandsInterfaces, sPhoneSwitcher);
sNotificationChannelController = new NotificationChannelController(context);
sTelephonyNetworkFactories = new TelephonyNetworkFactory[numPhones];
for (int i = 0; i < numPhones; i++) {
sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
sPhoneSwitcher, sc, sSubscriptionMonitor, Looper.myLooper(),
sContext, i, sPhones[i].mDcTracker);
}
}
}
}
經過上面兩段程式碼,我們看到了Phone物件的建立過程:
+ 先建立DefaultPhoneNotifier物件,該物件的作用是監聽RIL層發過來的Phone狀態變化;
+ 針對當前的網路型別建立不同的Java層RIL,即RILJ;
+ 拿到RILJ之後,就要利用DefaultPhoneNotifier和RILJ並根據當前的Phone型別(GSM/CDMA)來建立不同GsmCdmaPhone物件;
Telephony之GsmCdmaCallTracker
Application如果要發起通話相關的動作,可以通過Telephony的實體物件,也就是Phone物件來發起請求,而Phone物件就會通話相關的請求通過GsmCallTracker轉發給RILJ,然後傳遞給Modem。所以,GsmCallTracker是Phone物件和RILJ之間通話相關事務的接力者。
GsmCdmaCallTracker的作用及建立過程
首先看GsmCdmaCallTracker提供的功能
synchronized Connection dial (){}
void acceptCall () throws CallStateException {}
void rejectCall () throws CallStateException {}
void switchWaitingOrHoldingAndActive() throws CallStateException {}
void clearDisconnected() {}
boolean canDial() {}
private void updatePhoneState() {}
void hangup (GsmCdmaConnection conn) throws CallStateException {}
void hangup (GsmCdmaCall call) throws CallStateException {}
上述方法表明GsmCdmaCallTracker的作用包括兩方面:
+ 對通話線路進行操作,包括接聽、結束通話、切換、設定靜音等;
+ 對當前的通話狀態進行通知(IDEL、RINGING、OFFHOOK);
下面看初始化過程:
建立過程在GsmCdmaPhone中完成
public GsmCdmaPhone(Context context, CommandsInterface ci, PhoneNotifier notifier, int phoneId,
int precisePhoneType, TelephonyComponentFactory telephonyComponentFactory) {
this(context, ci, notifier, false, phoneId, precisePhoneType, telephonyComponentFactory);
}
public GsmCdmaPhone(Context context, CommandsInterface ci, PhoneNotifier notifier,
boolean unitTestMode, int phoneId, int precisePhoneType,
TelephonyComponentFactory telephonyComponentFactory) {
super(precisePhoneType == PhoneConstants.PHONE_TYPE_GSM ? "GSM" : "CDMA",
notifier, context, ci, unitTestMode, phoneId, telephonyComponentFactory);
// phone type needs to be set before other initialization as other objects rely on it
mPrecisePhoneType = precisePhoneType;
initOnce(ci);
initRatSpecific(precisePhoneType);
mSST = mTelephonyComponentFactory.makeServiceStateTracker(this, this.mCi);
// DcTracker uses SST so needs to be created after it is instantiated
mDcTracker = mTelephonyComponentFactory.makeDcTracker(this);
mSST.registerForNetworkAttached(this, EVENT_REGISTERED_TO_NETWORK, null);
mDeviceStateMonitor = mTelephonyComponentFactory.makeDeviceStateMonitor(this);
logd("GsmCdmaPhone: constructor: sub = " + mPhoneId);
}
GsmCdmaCallTracker構造方法
public GsmCdmaCallTracker (GsmCdmaPhone phone) {
this.mPhone = phone;
// 拿到RILJ
mCi = phone.mCi;
// 監聽通話、Radio狀態
mCi.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null);
mCi.registerForOn(this, EVENT_RADIO_AVAILABLE, null);
mCi.registerForNotAvailable(this, EVENT_RADIO_NOT_AVAILABLE, null);
// Register receiver for ECM exit
IntentFilter filter = new IntentFilter();
filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
mPhone.getContext().registerReceiver(mEcmExitReceiver, filter);
updatePhoneType(true);
}
在建構函式中GsmCdmaCallTracker拿到RILJ物件,當需要對當前通話連線操作時,就會直接呼叫RILJ去實現,同時在構造方法中又註冊了通話狀態和Radio的狀態監聽器,用於向其他物件通知當前Radio狀態的改變。
GsmCdmaCallTracker對通話動作的處理
通話動作包含接聽、結束通話、切換、靜音等,這些事件在APP層被請求後,最終都會發送給當前的Phone物件,也就是PhoneProxy,然後再轉交給當前的mActivePhone,也就是某個GSMCdmaPhone,此時GSMCdmaPhone物件就會把請求轉交給GsmCdmaCallTracker來處理
撥號:
//GSM
/**
* clirMode is one of the CLIR_ constants
*/
public synchronized Connection dial(String dialString, int clirMode, UUSInfo uusInfo,
Bundle intentExtras)
throws CallStateException {
// note that this triggers call state changed notif
// 清楚連結
clearDisconnected();
// 條件判斷
if (!canDial()) {
throw new CallStateException("cannot dial in current state");
}
String origNumber = dialString;
dialString = convertNumberIfNecessary(mPhone, dialString);
// The new call must be assigned to the foreground call.
// That call must be idle, so place anything that's
// there on hold
// 是否需要切換通話
if (mForegroundCall.getState() == GsmCdmaCall.State.ACTIVE) {
// this will probably be done by the radio anyway
// but the dial might fail before this happens
// and we need to make sure the foreground call is clear
// for the newly dialed connection
switchWaitingOrHoldingAndActive();
// This is a hack to delay DIAL so that it is sent out to RIL only after
// EVENT_SWITCH_RESULT is received. We've seen failures when adding a new call to
// multi-way conference calls due to DIAL being sent out before SWITCH is processed
try {
Thread.sleep(500);
} catch (InterruptedException e) {
// do nothing
}
// Fake local state so that
// a) foregroundCall is empty for the newly dialed connection
// b) hasNonHangupStateChanged remains false in the
// next poll, so that we don't clear a failed dialing call
fakeHoldForegroundBeforeDial();
}
if (mForegroundCall.getState() != GsmCdmaCall.State.IDLE) {
//we should have failed in !canDial() above before we get here
throw new CallStateException("cannot dial in current state");
}
boolean isEmergencyCall = PhoneNumberUtils.isLocalEmergencyNumber(mPhone.getContext(),
dialString);
// 準備新的通話連線
mPendingMO = new GsmCdmaConnection(mPhone, checkForTestEmergencyNumber(dialString),
this, mForegroundCall, isEmergencyCall);
mHangupPendingMO = false;
mMetrics.writeRilDial(mPhone.getPhoneId(), mPendingMO, clirMode, uusInfo);
if ( mPendingMO.getAddress() == null || mPendingMO.getAddress().length() == 0
|| mPendingMO.getAddress().indexOf(PhoneNumberUtils.WILD) >= 0) {
// Phone number is invalid
mPendingMO.mCause = DisconnectCause.INVALID_NUMBER;
// handlePollCalls() will notice this call not present
// and will mark it as dropped.
pollCallsWhenSafe();
} else {
// Always unmute when initiating a new call
// 設定非靜音模式
setMute(false);
// 向RIL層傳送撥號請求
mCi.dial(mPendingMO.getAddress(), clirMode, uusInfo, obtainCompleteMessage());
}
if (mNumberConverted) {
mPendingMO.setConverted(origNumber);
mNumberConverted = false;
}
// 更新通話狀態
updatePhoneState();
mPhone.notifyPreciseCallStateChanged();
return mPendingMO;
}
接聽動作:
public void acceptCall() throws CallStateException {
// FIXME if SWITCH fails, should retry with ANSWER
// in case the active/holding call disappeared and this
// is no longer call waiting
if (mRingingCall.getState() == GsmCdmaCall.State.INCOMING) {
Rlog.i("phone", "acceptCall: incoming...");
// Always unmute when answering a new call
setMute(false);
// 向RIL層傳送接聽的請求
mCi.acceptCall(obtainCompleteMessage());
} else if (mRingingCall.getState() == GsmCdmaCall.State.WAITING) {
if (isPhoneTypeGsm()) {
setMute(false);
} else {
GsmCdmaConnection cwConn = (GsmCdmaConnection)(mRingingCall.getLatestConnection());
// Since there is no network response for supplimentary
// service for CDMA, we assume call waiting is answered.
// ringing Call state change to idle is in GsmCdmaCall.detach
// triggered by updateParent.
cwConn.updateParent(mRingingCall, mForegroundCall);
cwConn.onConnectedInOrOut();
updatePhoneState();
}
// 切換通話
switchWaitingOrHoldingAndActive();
} else {
throw new CallStateException("phone not ringing");
}
}
拒接動作:
public void rejectCall() throws CallStateException {
// AT+CHLD=0 means "release held or UDUB"
// so if the phone isn't ringing, this could hang up held
if (mRingingCall.getState().isRinging()) {
// 拒接
mCi.rejectCall(obtainCompleteMessage());
} else {
throw new CallStateException("phone not ringing");
}
}
以上三個動作最後都要呼叫mCi物件來處理,這個物件就是RILJ,他會把請求傳送到RIL層來處理
GsmCdmaCallTracker對通話狀態的處理
在GsmCdmaCallTracker中完成了通話相關動作之後,就立刻更新當前的狀態併發送給Radio狀態監聽者。
例如,接聽電話時,當傳送了mCi.dial()的請求之後,就立刻呼叫updatePhoneState()進行狀態更新:
private void updatePhoneState() {
PhoneConstants.State oldState = mState;
// 獲取當前狀態
if (mRingingCall.isRinging()) {
mState = PhoneConstants.State.RINGING;
} else if (mPendingMO != null ||
!(mForegroundCall.isIdle() && mBackgroundCall.isIdle())) {
mState = PhoneConstants.State.OFFHOOK;
} else {
Phone imsPhone = mPhone.getImsPhone();
if ( mState == PhoneConstants.State.OFFHOOK && (imsPhone != null)){
imsPhone.callEndCleanupHandOverCallIfAny();
}
mState = PhoneConstants.State.IDLE;
}
if (mState == PhoneConstants.State.IDLE && oldState != mState) {
mVoiceCallEndedRegistrants.notifyRegistrants(
new AsyncResult(null, null, null));
} else if (oldState == PhoneConstants.State.IDLE && oldState != mState) {
mVoiceCallStartedRegistrants.notifyRegistrants (
new AsyncResult(null, null, null));
}
if (Phone.DEBUG_PHONE) {
log("update phone state, old=" + oldState + " new="+ mState);
}
if (mState != oldState) {
// 通知GsmCdmaPhone進行狀態廣播
mPhone.notifyPhoneStateChanged();
mMetrics.writePhoneState(mPhone.getPhoneId(), mState);
}
}
在這個過程中,最後要通過GsmCdmaPhone的notifyPhoneStateChanged()方法來通知其他物件
這樣一來,DefaultPhoneNotifier就將RILJ與TelephonyRegistry聯絡起來了,當RILJ接收到RIL上報的Phone狀態時,就會通過DefaultPhoneNotifier傳送給TelephonyRegistry。
GsmCdmaCallTracker的更新機制
手機通話功能可以支援多路通話。比如最基本的情況是,在和A通話過程中(線路A),有新的來電時(線路B),如果選擇接聽B,那麼A線路將處於“呼叫保持”狀態,此時如果B線路被結束通話,那麼A線路重新被啟用。
而GsmCdmaCallTracker的更新機制核心任務就是維護這些不同線路,包括對各個線路的操作(比如接聽、結束通話、保持),以及各個線路狀態的維護。為了達到這個目的,GsmCallTracker內部建立了兩個非常重要的物件:GsmCdmaConnection和GsmCdmaCall。
GsmCdmaConnection
為了管理不同的線路,Android定義了GsmCdmaConnection類,簡單來說,就是一條通話線路,就是一個GsmCdmaConnection型別的物件。
在GsmCdmaCallTracker的成員變數中,建立了GsmCdmaConnection型別的陣列變數來維護所有的線路
public static final int MAX_CONNECTIONS_GSM = 19; //7 allowed in GSM + 12 from IMS for SRVCC
private GsmCdmaConnection mConnections[];
mConnections = new GsmCdmaConnection[MAX_CONNECTIONS_GSM];
GsmCdmaCall
這個物件和GsmCdmaConnection的作用類似,每一個通話線路都可以是一個GsmCall物件,但實際上並不是一個GsmCdmaConnection對應一個GsmCdmaCall。
一個通話線路狀態分為以下9種
狀態 | 說明 |
---|---|
IDEL | 沒有通話 |
ACTIVE | 被啟用狀態 |
HOLDING | 被保持狀態 |
DIALING | 正在撥出狀態 |
ALERTING | 正在撥出已經處於響鈴的狀態 |
INCOMING | 正在來電狀態 |
WAITING | 已經通話中,又有新的來電 |
DISCONNECTED | 被結束通話 |
DISCONNECTING | 正在結束通話 |
在GsmCdmaCallTracker中,又將不同的線路狀態分為3種
- ForegroundCall
- BackgroundCall
- RingingCall
然後建立三個GsmCdmaCall物件
+ mForegroundCall
+ mBackgroundCall
+ mRingingCall
對應關係如下:
物件 | 狀態 |
---|---|
mForegroundCall | ACTIVE、DIALING、ALERTING |
mBackgroundCall | HOLDING |
mRingingCall | INCOMING、WAITING |
這樣做的好處是,GsmCdmaCall不再面對具體的線路,而是面對當前Phone的狀態,被啟用的線路就是mForegroundCall,被保持的線路就是mBackgroundCall,而正處於響鈴狀態的線路就是mRingingCall,從這裡我們可以想到,他和GsmConnection的區別在於,一個GsmCdmaCall可能包含多個GsmCdmaConnection物件(比如同時有兩通電話處於被保持狀態)。
而GsmCdmaCall要做的主要功能就是維護不同GsmCall的狀態。
GsmCdmaCallTracker的更新機制
GsmCdmaCallTracker執行機制的核心就是要及時更新GsmCdmaConnection和GsmCall的狀態,因此弄明白這兩個物件的更新機制就會明白GsmCdmaCallTracker的更新機制。
現在回到GsmCdmaCallTracker的構造方法中,剛才我們看到,GsmCdmaCallTracker在構造方法的最後註冊了對通話和Radio狀態的監聽器,下面我們從這些監聽器入手分析GsmCdmaCallTracker的執行機制。
第一個監聽器
mCi.registerForCallStateChanged(this, EVENT_CALL_STATE_CHANGE, null);
這個監聽器監聽的是RIL層通話的狀態,當有新的狀態到來時(比如新的來電),就會通過EVENT_CALL_STATE_CHANGE訊息通知到GsmCdmaCallTracker,然後就會在handleMessage中進行處理:
public void handleMessage (Message msg) {
AsyncResult ar;
switch (msg.what) {
case EVENT_REPOLL_AFTER_DELAY:
case EVENT_CALL_STATE_CHANGE:
//得到RIL層訊息,通話狀態有變
pollCallsWhenSafe();
break;
}
}
然後就會呼叫pollCallsWhenSafe()方法去獲取當前最新的通話狀態。
然後看看第二個監聽器
mCi.registerForOn(this, EVENT_RADIO_AVAILABLE, null);
這個監聽器是用來監聽Radio的可用狀態,當Radio的狀態上來後處理
public void handleMessage (Message msg) {
AsyncResult ar;
switch (msg.what) {
case EVENT_RADIO_AVAILABLE:
handleRadioAvailable();
break;
}
}
然後進入handleRadioAvailable()中處理:
protected void handleRadioAvailable() {
pollCallsWhenSafe();
}
這裡就與第一個監聽器一樣了,然後看看第三個監聽器:
mCi.registerForNotAvailable(this, EVENT_RADIO_NOT_AVAILABLE, null);
這個監聽器用來監聽Radio的不可用狀態,當監聽的訊息上來後,在handleMessage中處理:
public void handleMessage (Message msg) {
AsyncResult ar;
switch (msg.what) {
case EVENT_RADIO_NOT_AVAILABLE:
handleRadioNotAvailable();
break;
}
}
然後會進入handleRadioNotAvailable()的流程:
private void handleRadioNotAvailable() {
pollCallsWhenSafe();
}
接下來又是pollCallsWhenSafe()的操作,到這裡我們發現,在GsmCdmaCallTracker建構函式中註冊的三個監聽器,無論哪一個被觸發都會進入pollCallsWhenSafe的流程,接下來的分析我們將會看到,GsmCdmaCallTracker將會主動請求最新的通話狀態,然後根據當前狀態去更新GsmCdmaConnection和GsmCdmaCall物件。
然後看看pollCallsWhenSafe()的流程:
protected void pollCallsWhenSafe() {
mNeedsPoll = true;
if (checkNoOperationsPending()) {
mLastRelevantPoll = obtainMessage(EVENT_POLL_CALLS_RESULT);
// 通過RILJ獲取當前的最新通話狀態
mCi.getCurrentCalls(mLastRelevantPoll);
}
}
這裡看到,在pollCallsWhenSafe中通過RILJ(也就是mCi)去向Modem查詢當前的通話狀態,並註冊了回撥的訊息EVENT_POLL_CALLS_RESULT,當拿到Modem返回值後,就會再次通過handleMessage()來處理最新的通話狀態:
public void handleMessage (Message msg) {
AsyncResult ar;
switch (msg.what) {
case EVENT_POLL_CALLS_RESULT:
// 拿到最新通話狀態
ar = (AsyncResult)msg.obj;
if (msg == mLastRelevantPoll) {
mNeedsPoll = false;
mLastRelevantPoll = null;
handlePollCalls((AsyncResult)msg.obj);
}
break;
}
}
然後將資料拿到後,交由handlePollCalls()來處理,在這個方法裡,就需要將當前的Modem通話狀態資料進行解析,更新GsmCdmaConnection和GsmCdmaCall物件:
protected synchronized void handlePollCalls(AsyncResult ar) {
List polledCalls;
Connection newRinging = null; //or waiting
boolean hasNonHangupStateChanged = false; // Any change besides
boolean hasAnyCallDisconnected = false;
boolean needsPollDelay = false;
boolean unknownConnectionAppeared = false;
for (int i = 0, curDC = 0, dcSize = polledCalls.size() ; i < mConnections.length; i++) {
//拿到當前GsmCallTracker中的通話線路
GsmConnection conn = mConnections[i];
DriverCall dc = null;
if (curDC < dcSize) {
//拿到當前的Modem中的通話線路狀態
dc = (DriverCall) polledCalls.get(curDC);
if (dc.index == i+1) {
curDC++;
} else {
dc = null;
}
}
if (conn == null && dc != null) {
if (mPendingMO != null && mPendingMO.compareTo(dc)) {
//mConnections中沒有當前線路,而且當前線路是匹配mPendingMO的,說明是最新發起的撥出線路
mConnections[i] = mPendingMO;
mPendingMO.mIndex = i;
mPendingMO.update(dc);
mPendingMO = null;
if (mHangupPendingMO) {
//是否在撥出之後使用者立刻結束通話了線路
mHangupPendingMO = false;
try {
//結束通話這通線路
hangup(mConnections[i]);
} catch (CallStateException ex) {
}
return;
}
} else {
//Modem中有該線路,而GsmConnection中沒有該線路,說明有新的通話來臨,需要建立新的線路連線
mConnections[i] = new GsmConnection(mPhone.getContext(), dc, this, i);
if (mConnections[i].getCall() == mRingingCall) {
//新來電
newRinging = mConnections[i];
} else {
//異常通話線路
if (dc.state != DriverCall.State.ALERTING && dc.state != DriverCall.State.DIALING) {
mConnections[i].onConnectedInOrOut();
if (dc.state == DriverCall.State.HOLDING) {
mConnections[i].onStartedHolding();
}
}
unknownConnectionAppeared = true;
}
}
hasNonHangupStateChanged = true;
} else if (conn != null && dc == null) {
//Modem中已經沒有當前的連結,說明該線路已經被結束通話,需要從mConnections中刪除(置為null)
mDroppedDuringPoll.add(conn);
mConnections[i] = null;
} else if (conn != null && dc != null && !conn.compareTo(dc)) {
//Modem中的連結資訊與當前的不匹配,可能發生了掉話或者新的通話
mDroppedDuringPoll.add(conn);
//需要建立新的連結
mConnections[i] = new GsmConnection (mPhone.getContext(), dc, this, i);
if (mConnections[i].getCall() == mRingingCall) {
newRinging = mConnections[i];
}
hasNonHangupStateChanged = true;
} else if (conn != null && dc != null) {
//當前線路與Modem匹配,更新當前的鏈路資訊
boolean changed;
changed = conn.update(dc);
hasNonHangupStateChanged = hasNonHangupStateChanged || changed;
}
}
//異常
if (mPendingMO != null) {
mDroppedDuringPoll.add(mPendingMO);
mPendingMO = null;
mHangupPendingMO = false;
}
if (newRinging != null) {
//新的來電,需要通知registerForNewRingingConnection的監聽者
mPhone.notifyNewRingingConnection(newRinging);
}
//對於結束通話的連結,需要標明結束通話的原因
for (int i = mDroppedDuringPoll.size() - 1; i >= 0 ; i--) {
GsmConnection conn = mDroppedDuringPoll.get(i);
if (conn.isIncoming() && conn.getConnectTime() == 0) {
// Missed or rejected call
Connection.DisconnectCause cause;
if (conn.mCause == Connection.DisconnectCause.LOCAL) {
//被拒掉
cause = Connection.DisconnectCause.INCOMING_REJECTED;
} else {
//未接來電
cause = Connection.DisconnectCause.INCOMING_MISSED;
}
mDroppedDuringPoll.remove(i);
hasAnyCallDisconnected |= conn.onDisconnect(cause);
} else if (conn.mCause == Connection.DisconnectCause.LOCAL
|| conn.mCause == Connection.DisconnectCause.INVALID_NUMBER) {
mDroppedDuringPoll.remove(i);
hasAnyCallDisconnected |= conn.onDisconnect(conn.mCause);
}
}
if (mDroppedDuringPoll.size() > 0) {
mCi.getLastCallFailCause( obtainNoPollCompleteMessage(EVENT_GET_LAST_CALL_FAIL_CAUSE));
}
if (needsPollDelay) {
pollCallsAfterDelay();
}
if (newRinging != null || hasNonHangupStateChanged || hasAnyCallDisconnected) {
internalClearDisconnected();
}
//更新通話狀態
updatePhoneState();
if (unknownConnectionAppeared) {
mPhone.notifyUnknownConnection