Android 系統APN配置詳解
這些天一直在調系統原生的Settings.apk裡面APN配置的問題,在設置裡面手動增加了APN配置選項,但是在界面上還是看不到。所以跟了下代碼,原以為就是簡單的頁面顯示的問題,這一跟不要緊,一下就快追到HAL層去了(NND).
首先看Settings.apk的源碼,位於packages/apps/Settings/src/com/android/settings/目錄下:首先找到ApnSettings類,繼承於PreferenceActivity,並實現了Preference.OnPreferenceChangeListener接口。PreferencesActivity是Android中專門用來實現程序設置界面及參數存儲的一個Activity,這裡就不再贅述了。
[java]
public class ApnSettings extends PreferenceActivity implements
Preference.OnPreferenceChangeListener {
// 恢復出廠設置的URI
public static final String RESTORE_CARRIERS_URI = "content://telephony/carriers/restore";
// 普通URI,用於ContentPrivoder保存著APN配置信息
public static final String PREFERRED_APN_URI = "content://telephony/carriers/preferapn";
private static final Uri DEFAULTAPN_URI = Uri.parse(RESTORE_CARRIERS_URI);
private static final Uri PREFERAPN_URI = Uri.parse(PREFERRED_APN_URI);
// 兩個句柄,用於恢復出廠設置
private RestoreApnUiHandler mRestoreApnUiHandler;
private RestoreApnProcessHandler mRestoreApnProcessHandler;
private String mSelectedKey;
// 組播接收的Intent過濾器
private IntentFilter mMobileStateFilter;
private final BroadcastReceiver mMobileStateReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals(
TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
Phone.DataState state = getMobileDataState(intent);
switch (state) {
case CONNECTED:
if (!mRestoreDefaultApnMode) {
fillList();
} else {
showDialog(DIALOG_RESTORE_DEFAULTAPN);
}
break;
}
}
}
};
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
// 在activity創建的時候根據xml文件來配置視圖,
// 實際上 res/xml/apn_settings.xml這個文件就是一個空的PreferenceScreen
addPreferencesFromResource(R.xml.apn_settings);
getListView().setItemsCanFocus(true); // 如果有List則獲得焦點
// 這個創建一個Inter 過濾器,過濾的動作為 ACTION_ANY_DATA_CONNECTION_STATE_CHANGED
mMobileStateFilter = new IntentFilter(
TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
}
@Override
protected void onResume() {
super.onResume();
// 注冊一個廣播接受者
registerReceiver(mMobileStateReceiver, mMobileStateFilter);
if (!mRestoreDefaultApnMode) { // 如果不是恢復出廠設置
fillList(); // 填充Activity的ListView
} else {
showDialog(DIALOG_RESTORE_DEFAULTAPN);
}
}
}
1) 這裡首先在onCreate()方法中,根據apn_settings.xml文件來配置界面的視圖,實際上就是一個PreferenceScreen。創建一個Intent過濾器, 過濾動作為ACTION_ANY_DATA_CONNECTION_STATE_CHANGED。
2) 然後在onResume()方法中,注冊一個廣播接受者,當收到上面的ACTION_ANY_DATA_CONNECTION_STATE_CHANGED動作時,調用
mMobileStateReceiver的onReceive()方法。其目的是為了,當我們進入APN設置的時候,這是再插上SIM卡能顯示出APN的配置信息。然後判斷是不是需要恢復出廠設置,如果不是,則調用fillList()方法填充當前Activity,顯示出APN的配置信息。
3) 首先獲取系統屬性gsm.sim.operator.numeric,根據這個參數通過系統提供的ContentProvider查詢數據庫(位於/data/data/com.android.providers.Telephony下的telephony.db數據庫中carriers表中),獲得對應的配置信息。然後將其填充到每一個ApnPreference中,最後將每個ApnPreference顯示到當前的PreferenceGroup上。
[java]
private void fillList() {
// 獲取系統的gsm.sim.operator.numeric 屬性
String where = "numeric=\"" + android.os.SystemProperties.get(TelephonyProperties.PROPERTY_ICC_OPERATOR_NUMERIC, "")+ "\"";
// 調用系統提供的ContentProvider查詢數據庫
Cursor cursor = getContentResolver().query(Telephony.Carriers.CONTENT_URI, new String[] {
"_id", "name", "apn", "type"}, where, null,
Telephony.Carriers.DEFAULT_SORT_ORDER);
// 找到當前Activity中的PreferenceGroup
PreferenceGroup apnList = (PreferenceGroup) findPreference("apn_list");
apnList.removeAll();
ArrayList<Preference> mmsApnList = new ArrayList<Preference>();
mSelectedKey = getSelectedApnKey();
cursor.moveToFirst();
// 迭代查詢數據庫
while (!cursor.isAfterLast()) {
String name = cursor.getString(NAME_INDEX);
String apn = cursor.getString(APN_INDEX);
String key = cursor.getString(ID_INDEX);
String type = cursor.getString(TYPES_INDEX);
// 新建一個 ApnPreference,填充裡面控件的值
ApnPreference pref = new ApnPreference(this);
pref.setKey(key);
pref.setTitle(name);
pref.setSummary(apn);
pref.setPersistent(false);
pref.setOnPreferenceChangeListener(this);
boolean selectable = ((type == null) || !type.equals("mms"));
pref.setSelectable(selectable);
if (selectable) {
if ((mSelectedKey != null) && mSelectedKey.equals(key)) {
pref.setChecked();
}
apnList.addPreference(pref);
} else {
mmsApnList.add(pref);
}
cursor.moveToNext();
}
cursor.close();
for (Preference preference : mmsApnList) { // 將這個preference加入到apnList中
apnList.addPreference(preference);
}
}
這裡的ApnPreference是我們自己定義的一個類,繼承於Preference。這個類很簡單 就是根據R.layout.apn_preference_layout文件來對APN配置頁面的每一項進行布局的。主要是兩個TextView和一個RadioButton。
這裡我們已經知道了進入APN設置後,系統是如何將數據庫中已經存在的APN條目讀取出來並通過UI的形式顯示出來的。那麼我們又怎麼添加自己定義的APN配置信息呢?這就要用到Options Menu了,在手機上當我們按下Menu鍵的時候彈出一個列表,單擊這個列表每一項我們可以進入相應的Activity等。
[java]
@Override
public boolean onCreateOptionsMenu(Menu menu) { // 當按下Menu鍵的時候調用
super.onCreateOptionsMenu(menu);
menu.add(0, MENU_NEW, 0, // 增加兩個條目,分別用於增加APN和恢復出廠設置
getResources().getString(R.string.menu_new))
.setIcon(android.R.drawable.ic_menu_add);
menu.add(0, MENU_RESTORE, 0,
getResources().getString(R.string.menu_restore))
.setIcon(android.R.drawable.ic_menu_upload);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) { // 響應Menu按下的列表
case MENU_NEW: // 增加APN
addNewApn();
return true;
case MENU_RESTORE: // 恢復出廠設置
restoreDefaultApn();
return true;
}
return super.onOptionsItemSelected(item);
}
private void addNewApn() { // 啟動新的Activity,動作為Intent.ACTION_INSERT
startActivity(new Intent(Intent.ACTION_INSERT, Telephony.Carriers.CONTENT_URI));
}
// 響應 設置頁面每一行條目的單擊事件
@Override
public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
int pos = Integer.parseInt(preference.getKey());
Uri url = ContentUris.withAppendedId(Telephony.Carriers.CONTENT_URI, pos);
對當前選中頁面進行編輯,也是啟動一個Activity
startActivity(new Intent(Intent.ACTION_EDIT, url));
return true;
}
public boolean onPreferenceChange(Preference preference, Object newValue) {
Log.d(TAG, "onPreferenceChange(): Preference - " + preference
+ ", newValue - " + newValue + ", newValue type - "
+ newValue.getClass());
if (newValue instanceof String) {
setSelectedApnKey((String) newValue);
}
return true;
}
無論是增加APN還是對原有的APN條目進行編輯,我們都是通過進入一個新的Activity來完成的。下面我們找到匹配Intent.ACTION_EDIT,Intent.ACTION_INSERT
我們找到對應的Activity ApnEditor,ApnEditor也是一個繼承與PreferenceActivity的類,同時實現了SharedPreferences.onSharedPreferenceChangeListener和Preference.OnPreferenceChangeListener接口。
[java]
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.apn_editor);
sNotSet = getResources().getString(R.string.apn_not_set);
mName = (EditTextPreference) findPreference("apn_name");
mApn = (EditTextPreference) findPreference("apn_apn");
mProxy = (EditTextPreference) findPreference("apn_http_proxy");
mPort = (EditTextPreference) findPreference("apn_http_port");
mUser = (EditTextPreference) findPreference("apn_user");
mServer = (EditTextPreference) findPreference("apn_server");
mPassword = (EditTextPreference) findPreference("apn_password");
mMmsProxy = (EditTextPreference) findPreference("apn_mms_proxy");
mMmsPort = (EditTextPreference) findPreference("apn_mms_port");
mMmsc = (EditTextPreference) findPreference("apn_mmsc");
mMcc = (EditTextPreference) findPreference("apn_mcc");
mMnc = (EditTextPreference) findPreference("apn_mnc");
mApnType = (EditTextPreference) findPreference("apn_type");
mAuthType = (ListPreference) findPreference(KEY_AUTH_TYPE);
mAuthType.setOnPreferenceChangeListener(this);
mProtocol = (ListPreference) findPreference(KEY_PROTOCOL);
mProtocol.setOnPreferenceChangeListener(this);
mRoamingProtocol = (ListPreference) findPreference(KEY_ROAMING_PROTOCOL);
// Only enable this on CDMA phones for now, since it may cause problems on other phone
// types. (This screen is not normally accessible on CDMA phones, but is useful for
// testing.)
TelephonyManager tm = (TelephonyManager)getSystemService(TELEPHONY_SERVICE);
if (tm.getCurrentPhoneType() == Phone.PHONE_TYPE_CDMA) {
mRoamingProtocol.setOnPreferenceChangeListener(this);
} else {
getPreferenceScreen().removePreference(mRoamingProtocol);
}
mCarrierEnabled = (CheckBoxPreference) findPreference(KEY_CARRIER_ENABLED);
mBearer = (ListPreference) findPreference(KEY_BEARER);
mBearer.setOnPreferenceChangeListener(this);
mRes = getResources();
final Intent intent = getIntent();
final String action = intent.getAction();
mFirstTime = icicle == null;
if (action.equals(Intent.ACTION_EDIT)) {
mUri = intent.getData();
Log.w(TAG, "llping Edit action:"+mUri.toString());
} else if (action.equals(Intent.ACTION_INSERT)) {
if (mFirstTime || icicle.getInt(SAVED_POS) == 0) {
mUri = getContentResolver().insert(intent.getData(), new ContentValues());
} else {
mUri = ContentUris.withAppendedId(Telephony.Carriers.CONTENT_URI,
icicle.getInt(SAVED_POS));
}
og.w(TAG, "llping Insert action:"+mUri.toString());
mNewApn = true;
// If we were unable to create a new note, then just finish
// this activity. A RESULT_CANCELED will be sent back to the
// original activity if they requested a result.
if (mUri == null) {
Log.w(TAG, "Failed to insert new telephony provider into "
+ getIntent().getData());
finish();
return;
}
// The new entry was created, so assume all will end well and
// set the result to be returned.
setResult(RESULT_OK, (new Intent()).setAction(mUri.toString()));
} else {
finish();
return;
}
mCursor = managedQuery(mUri, sProjection, null, null);
mCursor.moveToFirst();
fillUi();
}