(1).应用程序中的入口
从settings应用程序中,选择移动网络,进入到 Phone应用程序的移动网络设置界面 (actionbar的应用程序图标可以明确看出,开始的时候以为还是在 settings程序中,直接去找,找了好半天也没有找到,最后才想起根据 settings列表的点击事件去查看执行了什么操作,才发现已经进入了 Phone程序,看来很多东西不能只凭眼睛看到的就下结论 ),在这里启用数据连接。对应源码为:
alps/packages/apps/Phone/src/com/android/phone/MobileNetworkSettings.java
现看看该类的注释:
Mobile network settings” screen. This preference screen lets you enable/disable mobile data, and control data
roaming and other network-specific mobile data features. It’s used on non-voice-capable tablets as well as regular
phone devices. Note that this PreferenceActivity is part of the phone app, even though you reach it from the “Wireless
& Networks” section of the main Settings app. It’s not part of the “Call settings” hierarchy that’s available from the
Phone app (see CallFeaturesSetting for that.)
移动网络设置界面。此首选项允许启用禁用移动数据网络,并且控制数据漫游以及其他指定的移动数据网络。它是用于 non-voice-capable平板电脑以及常规电话设备。 注意,这个PreferenceActivity 是phone应用程序 的一部分,即使你是通过 settings程序的“无线&网络”选项到达这里。这不是” Call Setting”层次结构的一部分。
//String keys for preference lookup
private static final String BUTTON_DATA_ENABLED_KEY = “button_data_enabled_key” ; //启用数据网络的key
private static final String BUTTON_DATA_USAGE_KEY = “button_data_usage_key” ;
private static final String BUTTON_PREFERED_NETWORK_MODE = “preferred_network_mode_key” ;
private static final String BUTTON_ROAMING_KEY = “button_roaming_key”;
private static final String BUTTON_CDMA_LTE_DATA_SERVICE_KEY = “cdma_lte_data_service_key” ;
通过 BUTTON_DATA_ENABLED_KEY 找到相应的 CheckBoxPreference,在onCreate() 方法中有:
@Override
protected void onCreate(Bundle icicle) {
super .onCreate(icicle);
addPreferencesFromResource(R.xml.network_setting);
……
mDataConnPref = (DefaultSimPreference) prefSet.findPreference( KEY_DATA_CONN);
mDataConnPref .setOnPreferenceChangeListener(this);
mButtonDataEnabled = (CheckBoxPreference) prefSet.findPreference(BUTTON_DATA_ENABLED_KEY );
mButtonDataRoam = (CheckBoxPreference) prefSet.findPreference(BUTTON_ROAMING_KEY);
mButtonDataRoam .setSummaryOn(mExtension .getRoamingSummary( this,R.string.roaming_enable));
mButtonDataRoam .setSummaryOff(mExtension .getRoamingSummary( this,R.string.roaming_disable));
mButtonPreferredNetworkMode = (ListPreference) prefSet.findPreference(
BUTTON_PREFERED_NETWORK_MODE );
mButtonDataUsage = prefSet.findPreference(BUTTON_DATA_USAGE_KEY);
……
}
从oncreate方法中看到 mButtonDataEnabled 没有注册 PreferenceChangeListener事件,因此将不会触发onPreferenceChange()方法,仅触发 onPreferenceTreeClick( PreferenceScreen preferenceScreen, Preference preference)方法。在 preferenceActivity中如果监听了PreferenceChangeListener和 OnPreferenceClickListener,那么这onPreferenceTreeClick ,onPreferenceChange, onPreferenceClick三者的触发是怎么回事呢?
a.先调用onPreferenceClick() 方法,如果该方法返回 true,则不再调用onPreferenceTreeClick方法 ; 如果 onPreferenceClick方法返回false ,则继续调用 onPreferenceTreeClick方法。
b.onPreferenceChange的方法独立与其他两种方法的运行。也就是说,它总是会运行。
补充:点击某个Preference 控件后,会先回调 onPreferenceChange()方法,即是否保存值,然后再回调 onPreferenceClick以及onPreferenceTreeClick() 方法,因此在 onPreferenceClick/onPreferenceTreeClick方法中我们得到的控件值就是最新的 Preference控件值。
详细可以看: http://blog.csdn.net/qinjuning/article/details/6710003
接下来看看当前 MobileNetworkSettings的onPreferenceTreeClick 方法:
public boolean onPreferenceTreeClick( PreferenceScreen preferenceScreen, Preference preference) {
……..
else if (preference == mButtonDataEnabled) {
if (DBG ) {
log(“onPreferenceTreeClick: preference == mButtonDataEnabled.”);
}
///M: change the interface definition for consistent_UI
if (!mExtension .dataEnableReminder(mButtonDataEnabled.isChecked(), this)) {
Log.d( LOG_TAG, “onPreferenceTreeClick: preference == mButtonDataEnabled.” );
if (mButtonDataEnabled .isChecked() && isSimLocked()) {
mCellConnMgr.handleCellConn(0, PIN1_REQUEST_CODE);
Log.d( LOG_TAG, “Data enable check change request pin single card” );
mButtonDataEnabled.setChecked( false);
} else {
mIsChangeData = true ;
NetworkInfo networkInfo = mConnService.getActiveNetworkInfo();
if (!(networkInfo != null && networkInfo.getType() == ConnectivityManager.TYPE_WIFI
&& networkInfo.isConnected())) {
showDialog( PROGRESS_DIALOG);
}
mConnService.setMobileDataEnabled( mButtonDataEnabled .isChecked());
mH.sendMessageDelayed( mH .obtainMessage( DATA_STATE_CHANGE_TIMEOUT), 30000);
if (mButtonDataEnabled .isChecked() &&
isNeedtoShowRoamingMsg()) {
mExtension.showWarningDlg( this,R.string.data_conn_under_roaming_hint);
}
///M: add for ATT requirement
mExtension.disableDataRoaming( mButtonDataRoam , mButtonDataEnabled.isChecked());
}
}
return true ;
}
…….
}
mConnService .setMobileDataEnabled( mButtonDataEnabled .isChecked()); 这里mConnService是 ConnectivityManager的实例,从这个方法之后,数据的启用将进入到 framework层.根据是否check决定开启或关闭数据网络。
(2).ConnectivityManager 中的setMobileDataEnabled(boolean)的调用实际是 ConnectivityService中的SetMobileDataEnable 方法
public void setMobileDataEnabled( boolean enabled) {
try {
mService.setMobileDataEnabled(enabled);
} catch (RemoteException e) {
}
}
(3).ConnectivityService 才是实际的网络管理核心。(标识 MTK的部分是MTK 进行的修改,这里可以跳过不看)该方法中先通过 enforceChangePermission()对调用程序进行是否有改变网络状态的权限的检查,如果没有,应用程序将会抛出异常被终止掉。然后发送消息 EVENT_SET_MOBILE_DATA(Message.what), 该消息将被内部类 InternalHandler的handleMessgae() 方法进行处理。
(/frameworks/base/services/java/com/android/server/ConnectivityService.java)
public void setMobileDataEnabled( boolean enabled) {
// M: To check permission for Mobile Manager Service/Application. @{
if (FeatureOption .MTK_MOBILE_MANAGEMENT) {
if (enabled && !checkMoMSSubPermission(SubPermissions.CHANGE_NETWORK_STATE_ON)) {
Slog. e(TAG, “setMobileDataEnabled(” + enabled + “) is lack of permission CHANGE_NETWORK_STATE_ON”);
return;
}
}
// @}
enforceChangePermission();
// MTK start
if (FeatureOption.MTK_GEMINI_SUPPORT) {
int curSlotId = Settings.System.getInt( mContext.getContentResolver(), Settings.System.GPRS_CONNECTION_SETTING , Settings.System.GPRS_CONNECTION_SETTING_DEFAULT ) - 1;
Log. e(“SJ”, “setMobileDataEnabled(” + enabled + “): curSlotId=” + curSlotId);
if (DBG ) Slog.d ( TAG, “setMobileDataEnabled(” + enabled + “): curSlotId=” + curSlotId);
if (enabled && (curSlotId == SimInfo. SLOT_NONE)) {
try{
mITelephony = getITelephony();
if( mITelephony == null){
Slog. e(TAG, “NULL in mITelephony”);
return;
}
for (int simId=PhoneConstants.GEMINI_SIM_1; simId
}
public boolean getAnyDataEnabled() {
final boolean result;
synchronized (mDataEnabledLock ) {
mUserDataEnabled = Settings.Global.getInt(
mPhone. getContext().getContentResolver(), Settings.Global. MOBILE_DATA, 1) == 1;
result = ( mInternalDataEnabled && sPolicyDataEnabled
&& ( mUserDataEnabled
|| ( mRequestedApnType.equals(PhoneConstants. APN_TYPE_MMS) && dataEnabled[DctConstants.APN_MMS_ID]))
&& ( enabledCount != 0));
}
if (!result && DBG ) log(“getAnyDataEnabled ” + result);
return result;
}
(9).调用onTrySetupData(String reason) 方法是DataConnectionTracker定义的抽象方法,将由其子类进行具体的实现。 DataConnectionTracker有2 个子类,分别是: GsmDataConnectionTracker和CdmaDataConnectionTracker 。这里将根据 sim支持的网络制式进行分支了。
(frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GsmDataConnectionTracker.java)
(frameworks/opt/telephony/src/java/com/android/internal/telephony/cdma/CdmaDataConnectionTracker.java )
GsmDataConnectionTracker 中的实现如下 :
protected boolean onTrySetupData(String reason) {
if (DBG ) log("onTrySetupData: reason=" + reason);
setupDataOnReadyApns(reason);
return true ;
}
CdmaDataConnectionTracker中实现如下:
protected boolean onTrySetupData(String reason) {
return trySetupData(reason);
}
两个分支中都是又调了其他的方法。 GsmDataConnectionTracker比CdmaDataConnectionTracker 多调了setupDataOnReadyApns方法,之后也会调用 trySetupdata()方法。
(10).先来看GsmDataConnectionTracker 分支,setupDataOnReadyApns(String reason)方法:根据当中的注释可知:这里循环遍历 mDataConnectionAsyncChannels 停止所有的数据链接的重连尝试闹钟。此后再一个遍历将 apnContext重置状态为idle ,以便能够执行到 trySetupdata()方法。
private void setupDataOnReadyApns (String reason) {
// Stop reconnect alarms on all data connections pending
// retry. Reset ApnContext state to IDLE.
for (DataConnectionAc dcac : mDataConnectionAsyncChannels .values()) {
if (dcac.getReconnectIntentSync() != null) {
cancelReconnectAlarm(dcac);
}
// update retry config for existing calls to match up
// ones for the new RAT.
if (dcac.dataConnection != null ) {
Collection apns = dcac.getApnListSync();
boolean hasDefault = false ;
for (ApnContext apnContext : apns) {
if (apnContext.getApnType().equals(PhoneConstants. APN_TYPE_DEFAULT)) {
hasDefault = true;
break;
}
}
configureRetry(dcac. dataConnection, hasDefault, 0);
}
}
// Be sure retry counts for Apncontexts and DC's are sync'd.
// When DCT/ApnContexts are refactored and we cleanup retrying
// this won't be needed.
resetAllRetryCounts();
// Only check for default APN state
for (ApnContext apnContext : mApnContexts .values()) {
if (apnContext.getState() == DctConstants.State. FAILED) {
// By this time, alarms for all failed Apns
// should be stopped if any.
// Make sure to set the state back to IDLE
// so that setup data can happen.
apnContext.setState(DctConstants.State. IDLE);
}
if (apnContext.isReady()) {
if (apnContext.getState() == DctConstants.State. IDLE ||
apnContext.getState() == DctConstants.State. SCANNING) {
Log. e("SJ", "setupDataOnReadyApns-->apnType" +apnContext.getApnType()+ "apnContext "+apnContext.toString());
apnContext.setReason(reason);
Log. e("SJ", "then enter trySetupData()" );
trySetupData(apnContext);
}
}
}
}
(11).Gsm的trySetupData 看起来感觉非常复杂。如果代码是在模拟器上的话,这里就更新 apnContext的连接状态为Connected,并且通过 mPhone提示数据连接已经改变。之后若成功的话将调用 setupData(apnContext);
private boolean trySetupData(ApnContext apnContext) {
String apnType = apnContext.getApnType();
if (mPhone .getSimulatedRadioControl() != null ) {
// Assume data is connected on the simulator
// FIXME this can be improved
apnContext.setState(DctConstants.State. CONNECTED);
mPhone.notifyDataConnection(apnContext.getReason(), apnType);
return true ;
}
//MTK begin
if( FeatureOption.MTK_GEMINI_SUPPORT && PhoneConstants. APN_TYPE_DEFAULT .equals(apnType)){
int gprsDefaultSIM = getDataConnectionFromSetting();
GeminiPhone mGeminiPhone = (GeminiPhone)PhoneFactory.getDefaultPhone ();
logd( "gprsDefaultSIM:" + gprsDefaultSIM);
if(gprsDefaultSIM != mGsmPhone .getMySimId()){
logd( "The setting is off(1)" );
Log. e("SJ", "MTK stoped it,gprsDefaultSIM!=mGsmPhone.getMySimId" );
return false ;
} else if (gprsDefaultSIM < 0){
logd( "The setting is off(2)" );
Log. e("SJ", "MTK stoped it,gprsDefaultSIM<0" );
return false ;
} else if ( mGeminiPhone != null && mGeminiPhone.isGprsDetachingOrDetached( mGsmPhone .getMySimId()) &&
!TextUtils. equals(apnContext.getReason(), Phone.REASON_DATA_ATTACHED)) {
logd( "trySetupData: detaching or detached state." );
Log. e("SJ", "MTK stoped it,mGeminiPhone " );
return false ;
}
}
//MTK end
if (apnContext.getState() == DctConstants.State. DISCONNECTING) {
apnContext.setReactive( true);
}
boolean desiredPowerState = mPhone .getServiceStateTracker().getDesiredPowerState();
boolean anyDataEnabled = (FeatureOption.MTK_BSP_PACKAGE ||
!isDataAllowedAsOff(apnType))? getAnyDataEnabled() : isNotDefaultTypeDataEnabled();
if ((apnContext.getState() == DctConstants.State. IDLE ||
apnContext.getState() == DctConstants.State. SCANNING) &&
isDataAllowed(apnContext) && anyDataEnabled && !isEmergency()) {
if (apnContext.getState() == DctConstants.State. IDLE) {
ArrayList<ApnSetting> waitingApns = buildWaitingApns(apnType);
if (waitingApns.isEmpty()) {
if (DBG ) log("trySetupData: No APN found");
notifyNoData(GsmDataConnection.FailCause. MISSING_UNKNOWN_APN, apnContext);
notifyOffApnsOfAvailability(apnContext.getReason());
return false ;
} else {
apnContext.setWaitingApns(waitingApns);
if (DBG ) {
log ( "trySetupData: Create from mAllApns : " + apnListToString( mAllApns));
}
}
}
if (DBG ) {
log ( "Setup watingApns : " + apnListToString(apnContext.getWaitingApns()));
}
boolean retValue = setupData(apnContext);
notifyOffApnsOfAvailability(apnContext.getReason());
return retValue;
} else {
// TODO: check the condition.
if (!apnContext.getApnType().equals(PhoneConstants. APN_TYPE_DEFAULT)
&& (apnContext.getState() == DctConstants.State. IDLE
|| apnContext.getState() == DctConstants.State. SCANNING))
mPhone.notifyDataConnectionFailed(apnContext.getReason(), apnType);
notifyOffApnsOfAvailability(apnContext.getReason());
return false ;
}
}
(12).setupData()中将通过bringup 方法建立数据连接.这个方法相当复杂,没有仔细去研究,留待后面有时间再处理吧。
private boolean setupData(ApnContext apnContext) {
ApnSetting apn;
GsmDataConnection dc;
int profileId = getApnProfileID(apnContext.getApnType());
apn = apnContext.getNextWaitingApn();
if (apn == null ) {
if (DBG ) log(“setupData: return for no apn found!”);
return false ;
}
dc = (GsmDataConnection) checkForConnectionForApnContext(apnContext);
// M: check if the dc's APN setting is prefered APN for default connection.
if (dc != null && PhoneConstants. APN_TYPE_DEFAULT.equals(apnContext.getApnType())) {
ApnSetting dcApnSetting = dc.getApnSetting();
if (dcApnSetting != null && !dcApnSetting. apn.equals(apn.apn )) {
if (DBG ) log("The existing DC is not using prefered APN.");
Log. e("SJ", "SetUpData-->The existing DC is not using prefered APN." );
dc = null;
}
}
if (dc == null ) {
dc = findReadyDataConnection(apn);
if (dc == null ) {
if (DBG ) log("setupData: No ready GsmDataConnection found!");
// TODO: When allocating you are mapping type to id. If more than 1 free,
// then could findFreeDataConnection get the wrong one??
dc = findFreeDataConnection();
}
if (dc == null ) {
dc = createDataConnection();
}
if (dc == null ) {
if (PhoneFactory.isDualTalkMode()) {
//M: in dual-talk project, we only have single pdp ability.
if (apnContext.getApnType() == PhoneConstants. APN_TYPE_DEFAULT)
{
if (DBG ) log("setupData: No free GsmDataConnection found!");
return false ;
}
ApnContext DisableapnContext = mApnContexts.get(PhoneConstants.APN_TYPE_DEFAULT);
clearWaitingApn();
cleanUpConnection( true, DisableapnContext);
//disableApnType(PhoneConstants.APN_TYPE_DEFAULT);
mWaitingApnList.add(apnContext.getApnType());
return true ;
}
} else {
if (DBG ) log("setupData: No free GsmDataConnection found!");
return false ;
}
}
} else {
apn = mDataConnectionAsyncChannels.get(dc.getDataConnectionId()).getApnSettingSync();
}
DataConnectionAc dcac = mDataConnectionAsyncChannels.get(dc.getDataConnectionId());
dc.setProfileId( profileId ); // assumed no connection sharing on profiled types
int refCount = dcac.getRefCountSync();
if (DBG ) log("setupData: init dc and apnContext refCount=" + refCount);
// configure retry count if no other Apn is using the same connection.
if (refCount == 0) {
configureRetry(dc, apn.canHandleType(PhoneConstants. APN_TYPE_DEFAULT),
apnContext.getRetryCount());
}
if (apnContext.getDataConnectionAc() != null && apnContext.getDataConnectionAc() != dcac) {
if (DBG ) log("setupData: dcac not null and not equal to assigned dcac.");
apnContext.setDataConnectionAc( null);
}
apnContext.setDataConnectionAc(dcac);
apnContext.setDataConnection(dc);
apnContext.setApnSetting(apn);
apnContext.setState(DctConstants.State. INITING);
mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
// If reconnect alarm is active on this DataConnection, wait for the alarm being
// fired so that we don't disruppt data retry pattern engaged.
if (apnContext.getDataConnectionAc().getReconnectIntentSync() != null) {
if (DBG ) log("setupData: data reconnection pending");
apnContext.setState(DctConstants.State. FAILED);
if (PhoneConstants.APN_TYPE_MMS.equals(apnContext.getApnType())) {
mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType(), PhoneConstants.DataState.CONNECTING );
} else {
mPhone.notifyDataConnection(apnContext.getReason(), apnContext.getApnType());
}
return true ;
}
if (apnContext.getApnType() == PhoneConstants. APN_TYPE_MMS) {
mWaitingApnList.clear();
/**
* M: if MMS's proxy IP address is the same as current connected interface
* and their APN is not the same, try to disable the existed one.
* Then, setup MMS's interface.
*/
for (ApnContext currApnCtx : mApnContexts .values()) {
ApnSetting apnSetting = currApnCtx.getApnSetting();
if (currApnCtx == apnContext)
continue;
if ((apnSetting != null ) && !currApnCtx.isDisconnected() &&
!apnSetting.equals(apn) && (isSameProxy(apnSetting, apn) && !apnSetting.apn.equals(apn.apn ))) {
if (DBG ) logd("setupData: disable conflict APN " + currApnCtx.getApnType());
disableApnType(currApnCtx.getApnType());
mWaitingApnList.add(currApnCtx.getApnType());
}
}
}
Message msg = obtainMessage();
msg. what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
msg. obj = apnContext;
dc.bringUp(msg, apn);
if (DBG ) log("setupData: initing!");
return true ;
}
(13).再回过头来看看CdmaDataConnectionTracker的 trySetupData()方法。与Gsm 相同,这里也处理了模拟器的连接状态处理。之后也会调用 setupData(String reason),此后调用notifyoffApnsOfAvailability方法通知一些 apn的连接状态是DISCONNECTED,在 notifyoffApnsOfAvailability方法中循环遍历所有的apn,通过 isApnIdEnabled(int id)将所有未启用的apn筛选出来并利用 avail/unavail notificiations(notifyApnIdDisconnected方法 )送出去。—-当然这里为什么要发出去,目的是什么有什么作用,我还不太能理解清楚。
private boolean trySetupData(String reason) {
if (DBG ) log(“***trySetupData due to ” + (reason == null ? “(unspecified)” : reason));
if (mPhone .getSimulatedRadioControl() != null ) {
// Assume data is connected on the simulator
// FIXME this can be improved
setState(DctConstants.State. CONNECTED);
notifyDataConnection(reason);
notifyOffApnsOfAvailability(reason);
log( "(fix?) We're on the simulator; assuming data is connected" );
return true ;
}
int psState = mCdmaPhone .mSST .getCurrentDataConnectionState();
boolean roaming = mPhone .getServiceState().getRoaming();
boolean desiredPowerState = mCdmaPhone .mSST .getDesiredPowerState();
if ((mState == DctConstants.State.IDLE || mState == DctConstants.State.SCANNING) &&
isDataAllowed() && getAnyDataEnabled() && !isEmergency()) {
boolean retValue = setupData(reason);
notifyOffApnsOfAvailability(reason);
return retValue;
} else {
notifyOffApnsOfAvailability(reason);
return false ;
}
}
protected void notifyOffApnsOfAvailability(String reason) {
if (DBG ) log(“notifyOffApnsOfAvailability - reason= ” + reason);
for (int id = 0; id < DctConstants.APN_NUM_TYPES; id++) {
if (id == apnTypeToId(apnIdToType(id)) && !isApnIdEnabled(id)) {
notifyApnIdDisconnected(reason, id);// 通知该apn的状态是断开的
Log. e(“SJ”, “notify disconnected–> id ” +id+” reason “+reason);
}
}
}
// since we normally don’t send info to a disconnected APN, we need to do this specially
//通常我们不会给一个断开的 apn发送消息,因此我们需要专门来做这个
private void notifyApnIdDisconnected(String reason, int apnId) {
mPhone.notifyDataConnection(reason, apnIdToType(apnId), PhoneConstants.DataState.DISCONNECTED );
}
(14).接下来在看一下CdmaDataConnectionTracker的 setupData(reason)方法。和gsm 相同,都是寻找对应的 DataConnection再根据其bringup() 方法建立数据连接,并带上了 DctConstants. EVENT_DATA_SETUP_COMPLETE 的消息。注意:当该动作完成后才被回调该消息
private boolean setupData(String reason) {
CdmaDataConnection conn = findFreeDataConnection();
if (conn == null ) {
if (DBG ) log("setupData: No free CdmaDataConnection found!");
return false ;
}
/** TODO: We probably want the connection being setup to a parameter passed around */
mPendingDataConnection = conn;
String[] types;
int apnId;
if (mRequestedApnType .equals(PhoneConstants.APN_TYPE_DUN)) {
types = mDunApnTypes;
apnId = DctConstants. APN_DUN_ID;
} else {
types = mDefaultApnTypes;
apnId = mDefaultApnId;
}
mActiveApn = new ApnSetting(apnId, "", "", "" , "", "", "" , "" , "" , "" ,
"", 0, types, "IP" , "IP" , true , 0);
if (DBG ) log("call conn.bringUp mActiveApn=" + mActiveApn );
Message msg = obtainMessage();
msg. what = DctConstants.EVENT_DATA_SETUP_COMPLETE;
msg. obj = reason;
conn.bringUp(msg, mActiveApn);
setState(DctConstants.State. INITING);
notifyDataConnection(reason);
return true ;
}
(15).在GsmDataConnectionTracker 和CdmaDataConnectionTracker的 setup之后,回调到DataConnection的 bringUp方法。它将之前标记what为 DctConstants. EVENT_DATA_SETUP_COMPLETE 的消息和传递过来的 apnSetting实例构造成新的连接参数 ConnectionParams并发连接消息让DataConnection状态机对应的状态进行处理。 ConnectionParams是在 DataConnection的内部类,用来保存连接的参数信息。
public void bringUp (Message onCompletedMsg, ApnSetting apn) {
sendMessage(obtainMessage( EVENT_CONNECT, new ConnectionParams(apn, onCompletedMsg)));
}
/**
* Used internally for saving connecting parameters.
*/
protected static class ConnectionParams {
public ConnectionParams(ApnSetting apn, Message onCompletedMsg) {
this. apn = apn;
this. onCompletedMsg = onCompletedMsg;
}
public int tag ;//这个tag 还不知道有什么作用
public ApnSetting apn ;
public Message onCompletedMsg ;
}
(16).DataConnection 继承了StateMachine,关于状态机可以参考: http://blog.csdn.net/wsb1321/article/details/8021620
DataConnection一共有6 个状态,比起 wifi的状态要少很多,结构也相对简单很多。看一下其结构图:
(frameworks/opt/telephony/src/java/com/android/internal/telephony/DataConnection.java)
上面图结构根据构造方法可得.根据 setInitialState( mInactiveState )可知mInactiveState 为状态机初始状态
protected DataConnection(PhoneBase phone, String name, int id, RetryManager rm,
DataConnectionTracker dct) {
super(name);
setLogRecSize(100);
if (DBG ) log("DataConnection constructor E");
this. phone = phone;
this. mDataConnectionTracker = dct;
mId = id;
mRetryMgr = rm;
this. cid = -1;
setDbg( false);
addState( mDefaultState);
addState( mInactiveState, mDefaultState);
addState( mActivatingState, mDefaultState);
addState( mActiveState, mDefaultState);
addState( mDisconnectingState, mDefaultState);
addState( mDisconnectingErrorCreatingConnection , mDefaultState );
setInitialState( mInactiveState);// 设置初始状态
mApnList = new ArrayList<ApnContext>();
if (DBG ) log("DataConnection constructor X");
}
(17).mInactiveState为初始状态,因此当bringUp()调用时,发过来的EVENT_CONNECT消息将被它处理。看它的 processMessage方法:
@Override
public boolean processMessage (Message msg) {
boolean retVal;
......
case EVENT_CONNECT :
ConnectionParams cp = (ConnectionParams) msg. obj;
cp. tag = mTag ;//首次进入的话, mTag值应该为1
if (DBG ) {
log( "DcInactiveState msg.what=EVENT_CONNECT." + "RefCount = "
+ mRefCount);
}
mRefCount = 1;
onConnect(cp);
transitionTo( mActivatingState);
retVal = HANDLED;
break;
.....
}
关于 mTag的作用不是很理解,整个 DataConnction中在 仅mInactiveState的 enter方法中进行了值修改。
public void enter() {
mTag += 1;
....
}
此后调用传入connectionParams到onConnect方法,该方法是一个抽象方法,由GsmDataConnection和CdmaDataConnection实现。
(/frameworks/opt/telephony/src/java/com/android/internal/telephony/gsm/GsmDataConnection.java )
(frameworks/opt/telephony/src/java/com/android/internal/telephony/cdma/CdmaDataConnection.java )
(18).先看GsmDataConnection 的onConnect方法。该方法通过调用 setupDataCal()开始进行数据连接,并且将连接参数 cp通过EVENT_SETUP_DATA_CONNECTION_DONE 消息在AsyncResult.userObj返回出去。
protected void onConnect(ConnectionParams cp) {
mApn = cp.apn ;
if (DBG ) log("Connecting to carrier: '" + mApn .carrier
+ "' APN: '" + mApn .apn
+ "' proxy: '" + mApn .proxy + "' port: '" + mApn .port);
createTime = -1;
lastFailTime = -1;
lastFailCause = FailCause.NONE;
// msg.obj will be returned in AsyncResult.userObj;
Message msg = obtainMessage( EVENT_SETUP_DATA_CONNECTION_DONE , cp);
msg. obj = cp;
int authType = mApn .authType ;
if (authType == -1) {
authType = TextUtils. isEmpty(mApn. user) ? RILConstants. SETUP_DATA_AUTH_NONE
: RILConstants. SETUP_DATA_AUTH_PAP_CHAP;
}
/*String protocol;
if (phone.getServiceState().getRoaming()) {
protocol = mApn.roamingProtocol;
} else {
protocol = mApn.protocol;
}*/
//since in APN Editor, the roaming protocol item is showed only the current phone type is CDMA
//so here we align this design and use protocol even if it is roaming
String protocol = mApn. protocol ;
phone. mCM .setupDataCall(
Integer. toString(getRilRadioTechnology(RILConstants. SETUP_DATA_TECH_GSM)),
Integer. toString(mProfileId),
mApn. apn , mApn. user, mApn .password,
Integer. toString(authType),
protocol, String. valueOf(mId + 1), msg);
}
(19).再看下CdmaDataConnection中的onConnect()方法。从做的处理来看与Gsm的几乎没什么差别,但在调用setupDataCall方法时,参数有所不同。setupDataCall(String radioTechnology, String profile,String apn, String user, String password, String authType, String protocol, Message result) ,cdma并未进行 apn,user ,password的三个参数的传递。它调用的 setupDataCall其实与GsmDataConnection 中的setupDataCall也不相同,不过呢最终 CdmaDataConnection的setupDataCall 方法最终调用的地方与 GsmDataConntion相同。
@Override
protected void onConnect(ConnectionParams cp) {
if (DBG ) log("CdmaDataConnection Connecting...");
mApn = cp.apn ;
createTime = -1;
lastFailTime = -1;
lastFailCause = FailCause.NONE;
int dataProfile;
if ((cp.apn != null ) && (cp. apn .types .length > 0) && (cp. apn .types [0] != null ) &&
(cp. apn. types [0].equals(PhoneConstants. APN_TYPE_DUN))) {
if (DBG ) log("CdmaDataConnection using DUN");
dataProfile = RILConstants. DATA_PROFILE_TETHERED;
} else {
dataProfile = RILConstants. DATA_PROFILE_DEFAULT;
}
// msg.obj will be returned in AsyncResult.userObj;
Message msg = obtainMessage( EVENT_SETUP_DATA_CONNECTION_DONE , cp);
msg. obj = cp;
phone. mCM .setupDataCall(
Integer. toString(getRilRadioTechnology(RILConstants. SETUP_DATA_TECH_CDMA)),
Integer. toString(dataProfile),
null, null , null ,
Integer. toString(RILConstants.SETUP_DATA_AUTH_PAP_CHAP),
RILConstants. SETUP_DATA_PROTOCOL_IP, msg);
}
(20).setupDataCall方法的实现在 RIL.jvaa中
(frameworks/opt/telephony/src/java/com/android/internal/telephony/RIL.java)
CdmaDataConnection 中调用的是下面这个方法:
public void setupDataCall(String radioTechnology, String profile, String apn,
String user, String password, String authType, String protocol,
Message result) {
/* [Note by mtk01411] In original Android2.1 release: MAX PDP Connection is 1
* request_cid is only allowed to set as "1" manually
*/
String request_cid = "1";
setupDataCall(radioTechnology, profile, apn, user, password, authType, protocol, request_cid, result);
}
实际上只是为了增加一个 request_cid 而已,之后调用的其实和 GsmDataConnetion是一样的
public void setupDataCall(String radioTechnology, String profile, String apn,
String user, String password, String authType, String protocol, String requestCid,
Message result) {
RILRequest rr
= RILRequest. obtain(RIL_REQUEST_SETUP_DATA_CALL, result);
/* [Note by mtk01411] Currently, MAX PDP Connection is 1: request_cid is only allowed to set as "1" manually
* But for Multiple PDP Connecitons Feature:
* PdpConnection.java connect() shoud invoke this 8-parms version's setupDataCall() directly
* request_cid is obtained from the parameter paased into the setupDataCall()
*/
/* [Note by mtk01411] Change from 6 to 7: Due to add one cid field as the last one parameter */
rr. mp.writeInt(8);
rr. mp.writeString(radioTechnology);
rr. mp.writeString(profile);
rr. mp.writeString(apn);
rr. mp.writeString(user);
rr. mp.writeString(password);
rr. mp.writeString(authType);
/* [Add by mtk01411]
* MAX PDP Connection =1:Only cid string "1" is allowed for RILD
* MAX PDP Connection =2:Only cid string "1" or "2" is allowed for RILD
* MAX PDP Connection =3:Only cid string "1" or "2" or "3" is allowed for our RILD
*/
rr. mp.writeString(protocol);
rr. mp.writeString(requestCid);
if (RILJ_LOGD ) riljLog(rr.serialString() + "> "
+ requestToString(rr.mRequest) + " radioTech=" + radioTechnology + " profile="
+ profile + " apn=" + apn + " user=" + user + " password="
+ password + " authType=" + authType + " protocol=" + protocol + " requestCid=" + requestCid);
send(rr);// 发送请求,由 RIL的构造函数中创建的RILSender线程完成向 socket写入数据
}
(21).之后send 就是发消息给内部类 RILSender,再调用 socket发送数据了,当然再往下就是 c部分了,那里面是不会有对数据网络本身的开启关闭进行的处理的。因此从应用到框架的数据开启过程就是这样的流程了。
private void send(RILRequest rr) {
Message msg;
boolean show = (requestToString (rr. mRequest).compareTo("LAST_CALL_FAIL_CAUSE" ) == 0);
if (mSocket == null ) {
rr.onError( RADIO_NOT_AVAILABLE, null );
rr.release();
return;
}
msg = mSender.obtainMessage( EVENT_SEND, rr);
acquireWakeLock();
msg.sendToTarget();
}
之后 RILSender这个内部类调用LocalSocket进行数据发送
case EVENT_SEND:
/**
* mRequestMessagePending++ already happened for every
* EVENT_SEND, thus we must make sure
* mRequestMessagePending -- happens once and only once
*/
boolean alreadySubtracted = false ;
try {
LocalSocket s;
s = mSocket;
//MTK-START [mtk04070][111121][ALPS00093395]MTK modified
if (s == null || radioTemporarilyUnavailable !=RADIO_TEMPSTATE_AVAILABLE) {
rr.onError( RADIO_NOT_AVAILABLE, null );
rr.release();// 错误则将 RILRequest放回池中
mRequestMessagesPending--;
alreadySubtracted = true;
return;
}
//RILRequest请求列表,整型序列号serial作为其 id标识。
//当 RILSender发送一个RIL 请求后,则将其添加到该列表中(若发送时出现异常则需再清除);
//当请求完成并得到回送的 response消息后,则将其移除
synchronized (mRequestsList ) {
mRequestsList.add(rr);
}
mRequestMessagesPending--;
alreadySubtracted = true;
//MTK-END [mtk04070][111121][ALPS00093395]MTK modified
byte[] data;
//mp 即为rr中的 parcel,它当中是戴写入数据的缓冲区,经过 marshall优化后在赋值给data
data = rr. mp.marshall();
rr. mp.recycle();
rr. mp = null ;
if (data.length > RIL_MAX_COMMAND_BYTES ) {
throw new RuntimeException(
"Parcel larger than max bytes allowed! "
+ data.length);
}
// parcel length in big endian
dataLength[0] = dataLength [1] = 0;
dataLength[2] = ( byte)((data.length >> 8) & 0xff);
dataLength[3] = ( byte)((data.length ) & 0xff);
//Log.v(LOG_TAG, "writing packet: " + data.length + " bytes");
s.getOutputStream().write( dataLength);// 写入scoket数据长度,不能超过 RIL_MAX_COMMAND_BYTES
s.getOutputStream().write(data);// 写数据给 rild
} catch (IOException ex) {
Log. e(LOG_TAG, "IOException", ex);
// 如果发送过程中出现异常,通过 findAndRemoveRequestFromList移除该次RILRequest
req = findAndRemoveRequestFromList(rr. mSerial);
// make sure this request has not already been handled,
// eg , if RILReceiver cleared the list.
if (req != null || !alreadySubtracted) {
rr.onError( RADIO_NOT_AVAILABLE, null );
rr.release();
}
} catch (RuntimeException exc) {
Log. e(LOG_TAG, "Uncaught exception ", exc);
//如果发送过程中出现异常,通过 findAndRemoveRequestFromList移除该次RILRequest
req = findAndRemoveRequestFromList(rr. mSerial);
// make sure this request has not already been handled,
// eg , if RILReceiver cleared the list.
if (req != null || !alreadySubtracted) {
rr.onError( GENERIC_FAILURE, null );
rr.release();
}
} finally {
// Note: We are "Done" only if there are no outstanding
// requests or replies. Thus this code path will only release
// the wake lock on errors.
releaseWakeLockIfDone();
}
//MTK-START [mtk04070][111121][ALPS00093395]MTK modified
if (!alreadySubtracted) {
mRequestMessagesPending--;
}
//MTK-END [mtk04070][111121][ALPS00093395]MTK modified
break;
由此可见一个 RIL请求(RILRequest) 执行时一个异步过程,调用者调用 RIL类的api 函数只是往 RILSender线程添加了一个消息就返回,然后线程在执行无限循环时将其写入到 socket中,并将RILRequest 添加到一个列表中,再将数据优化后通过 socket写给rild 。