关于Telephony data 准备写三篇,本文是第一部分APN,根据Android O源码,简单总结了Android上APN相关的知识点。分以下三部分:
1. 预置APN数据加载
2. APN字段
3. APN的显示和编辑
APN(access point name)决定了我们手机接入网络的方式,一般我们手机都做了默认配置,也可以根据需要进行手动配置。APN存储在手机的“/data/user_de/0/com.android.providers.telephony/databases/”路径下的telephony.db中(多用户的情况下,只有owner才有这份数据),对应表是carriers。
1. 预置APN数据加载
关于db和表的创建,Subscription文章 TelephonyProvider部分已经写了,这里就不再赘述了。下面说下预置APN数据的加载过程。
我们预置的APN数据也是在db创建的过程中从配置文件读取然后加载到db的。
在db创建的过程中,DatabaseHelper.OnCreate方法会调用DatabaseHelper.initDatabase方法,该方法可以从配置文件中加载我们要预置的apn数据。
/**
* This function adds APNs from xml file(s) to db. The db may or may not be empty to begin
* with.
*/
private void initDatabase(SQLiteDatabase db) {
if (VDBG) log("dbh.initDatabase:+ db=" + db);
// Read internal APNS data
Resources r = mContext.getResources();
XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);//加载frameworks/base/core/res/res/xml/apns.xml 中的配置
int publicversion = -1;
try {
XmlUtils.beginDocument(parser, "apns");
publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
loadApns(db, parser);//调用loadApns方法完成加载任务。
} catch (Exception e) {
loge("Got exception while loading APN database." + e);
} finally {
parser.close();
}
// Read external APNS data (partner-provided)
XmlPullParser confparser = null;
//下面的部分获取指定路径下的APN配置文件。
//getApnConfFile方法用于获取配置文件,常用路径有三个:
//1. etc/apns-conf.xml
//2. telephony/apns-conf.xml
//3. misc/apns-conf.xml
//但是这三个文件并不会全部加载, 只加载最新修改的那个。
File confFile = getApnConfFile();
FileReader confreader = null;
if (DBG) log("confFile = " + confFile);
try {
confreader = new FileReader(confFile);
confparser = Xml.newPullParser();
confparser.setInput(confreader);
XmlUtils.beginDocument(confparser, "apns");
// Sanity check. Force internal version and confidential versions to agree
int confversion = Integer.parseInt(confparser.getAttributeValue(null, "version"));
if (publicversion != confversion) {
log("initDatabase: throwing exception due to version mismatch");
throw new IllegalStateException("Internal APNS file version doesn't match "
+ confFile.getAbsolutePath());
}
loadApns(db, confparser);//调用loadApns方法完成加载任务。
} catch (FileNotFoundException e) {
// It's ok if the file isn't found. It means there isn't a confidential file
// Log.e(TAG, "File not found: '" + confFile.getAbsolutePath() + "'");
} catch (Exception e) {
loge("initDatabase: Exception while parsing '" + confFile.getAbsolutePath() + "'" +
e);
} finally {
// Get rid of user/carrier deleted entries that are not present in apn xml file.
// Those entries have edited value USER_DELETED/CARRIER_DELETED.
if (VDBG) {
log("initDatabase: deleting USER_DELETED and replacing "
+ "DELETED_BUT_PRESENT_IN_XML with DELETED");
}
//到这里, 需要预置的APN已经全部加载到db中了, 但是db中可能存入了不必要的数据,
//下面的code是清除,更新db里的数据。
// Delete USER_DELETED
db.delete(CARRIERS_TABLE, IS_USER_DELETED + " or " + IS_CARRIER_DELETED, null);
//下面的code用于清除为了解决插入数据时的冲突而加了特殊标记的记录。
// Change USER_DELETED_BUT_PRESENT_IN_XML to USER_DELETED
ContentValues cv = new ContentValues();
cv.put(EDITED, USER_DELETED);
db.update(CARRIERS_TABLE, cv, IS_USER_DELETED_BUT_PRESENT_IN_XML, null);
// Change CARRIER_DELETED_BUT_PRESENT_IN_XML to CARRIER_DELETED
cv = new ContentValues();
cv.put(EDITED, CARRIER_DELETED);
db.update(CARRIERS_TABLE, cv, IS_CARRIER_DELETED_BUT_PRESENT_IN_XML, null);
if (confreader != null) {
try {
confreader.close();
} catch (IOException e) {
// do nothing
}
}
// Update the stored checksum
setApnConfChecksum(getChecksum(confFile));
}
if (VDBG) log("dbh.initDatabase:- db=" + db);
}
getApnConfFile方法用于获取配置文件,常用路径有三个,但是这三个文件并不会全部加载, 只加载最新修改的那个。
1. etc/apns-conf.xml
2. telephony/apns-conf.xml
3. misc/apns-conf.xml
private File getApnConfFile() {
// Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system".
File confFile = new File(Environment.getRootDirectory(), PARTNER_APNS_PATH);//etc/apns-conf.xml
File oemConfFile = new File(Environment.getOemDirectory(), OEM_APNS_PATH);//telephony/apns-conf.xml
File updatedConfFile = new File(Environment.getDataDirectory(), OTA_UPDATED_APNS_PATH);//misc/apns-conf.xml
confFile = getNewerFile(confFile, oemConfFile);
confFile = getNewerFile(confFile, updatedConfFile);
return confFile;
}
APN字段的定义在Telephony.java中的静态内部类Carriers中,总结如下:
字段名称 | 描述 |
---|---|
NAME | APN记录的名字; 我们手机里内置的运营APN会使用运营商的名字,例如”中国移动(China Mobile)Net” |
APN | APN的名字 |
PROXY | 代理地址 |
PORT | 代理端口 |
MMSPROXY | MMS代理地址 |
MMSPORT | MMS代理端口 |
SERVER | 服务器地址 |
USER | APN用户名 |
PASSWORD | APN用户名对应的密码 |
MMSC | 多媒体消息业务中心, 即彩信业务中心 |
MCC | 移动国家码 |
MNC | 移动网络码 |
NUMERIC | 运营商的数字代码,一般是MCC+MNC的形式,例如46001 |
AUTH_TYPE | 鉴权类型,一般使用的鉴权是PAP或CHAP |
TYPE | APN的类型,如mms,dun,ims等; 一个APN配置可以支持多种APN type, 不同类型用”,”分割 |
PROTOCOL | 连接APN所使用的协议,例如IP,IPV6,IPV4V6,PPP等 |
ROAMING_PROTOCOL | 漫游时所使用的协议 |
CURRENT | 添加新APN时,如果新APN和当前SIM的mcc,mnc相同,这个字段会被设置为1。 没有看到其他地方使用这个字段, 所以不太清楚这个字段的作用 |
CARRIER_ENABLED | 用于标识这个APN是否可用 |
BEARER | Radio Access Technology 信息, 当前可用的有LTE(14)和eHRPD(13) |
BEARER_BITMASK | Radio Access Technology bitmask, 用于标明当前APN可以包含的RAT; 0表示所有的RAT都可以,否则bitmask和RAT的关系是(1 << (RAT - 1)) |
MVNO_TYPE | 移动虚拟网络运营商(Mobile virtual network operator)的类型; 可用的数据有spn, IMSI和GID(Group Identifier Level 1) |
MVNO_MATCH_DATA | MVNO_TYPE数据, 这个值是和MVNO_TYPE对应的。 例如SPN: A MOBILE, BEN NL, …; IMSI: 302720x94, 2060188, …; GID: 4E, 33, … |
SUBSCRIPTION_ID | 用于表明这个APN属于哪个subscription, 这个值是从siminfo表中获取的 |
PROFILE_ID | profile是modem侧存储信息的方式, 这个值将APN和modem侧的profile联系起来 |
MODEM_COGNITIVE | 用于标明这个APN是否会在modem侧设置(没用过这个字段) |
MAX_CONNS | 该APN支持的最大连接数量 |
WAIT_TIME | 使用该APN进行数据连接时, 如果失败, retry要等待的时间 |
MAX_CONNS_TIME | |
MTU | 使用该APN建立的连接,可以传输的最大单元 |
EDITED | 该APN是否可以编辑 |
USER_VISIBLE | 是否可见,如果不可见, 那么我们在APN菜单里是看不到的 |
一般在手机的”settings->More->Mobile networks->Access point name”菜单中可以看到当前手机可用的Apn信息,也可以编辑添加Apn。进入菜单后的菜单如下图:
图例中显示了两个apn,点击任意一个可以进入编辑界面; 点击右上角的“+”号,可以添加新apn; 右上角的选项菜单用于将apn恢复成默认配置。
图片所示界面对应的代码是ApnSettings.java类,这是一个PreferenceFragment子类,中间有多层继承关系。ApnSettings作为一个fragment子类有自己的生命周期; 在启动时,OnResume()方法会调用fillList()放法,该方法完成了从db中查询Apn数据的任务,所以如果要完成一些APN的显示定制需求,可以在这个函数中修改sql语句。
private void fillList() {
final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
//mSubscriptionInfo是在onCreate()方法中根据intent中的参数从SubscriptionManager中获取的。
//即指定SIM卡对应的Subscription信息。
final int subId = mSubscriptionInfo != null ? mSubscriptionInfo.getSubscriptionId()
: SubscriptionManager.INVALID_SUBSCRIPTION_ID;
//获取mccmnc信息,这是查询APN信息的重要依据。
final String mccmnc = mSubscriptionInfo == null ? "" : tm.getSimOperator(subId);
Log.d(TAG, "mccmnc = " + mccmnc);
StringBuilder where = new StringBuilder("numeric=\"" + mccmnc +
"\" AND NOT (type='ia' AND (apn=\"\" OR apn IS NULL)) AND user_visible!=0");
//IMS APN是否需要隐藏可以在CarrierConfig中配置,
//mHideImsApn是在OnCreate方法中从CarrierConfigManager(CarrierConfigManager.KEY_HIDE_IMS_APN_BOOL)中获取的值
if (mHideImsApn) {
where.append(" AND NOT (type='ims')");
}
Cursor cursor = getContentResolver().query(Telephony.Carriers.CONTENT_URI, new String[] {
"_id", "name", "apn", "type", "mvno_type", "mvno_match_data"}, where.toString(),
null, Telephony.Carriers.DEFAULT_SORT_ORDER);
if (cursor != null) {
IccRecords r = null;
if (mUiccController != null && mSubscriptionInfo != null) {
//获取SIM卡对应的IcccRecords记录, 后面会用到。
r = mUiccController.getIccRecords(
SubscriptionManager.getPhoneId(subId), UiccController.APP_FAM_3GPP);
}
PreferenceGroup apnList = (PreferenceGroup) findPreference("apn_list");
apnList.removeAll();
//下面创建了四个ArrayList对象, 用于存储不同类型的APN数据。
ArrayList mnoApnList = new ArrayList();
ArrayList mvnoApnList = new ArrayList();
ArrayList mnoMmsApnList = new ArrayList();
ArrayList mvnoMmsApnList = new ArrayList();
mSelectedKey = getSelectedApnKey();
cursor.moveToFirst();
while (!cursor.isAfterLast()) {
String name = cursor.getString(NAME_INDEX);//APN记录的名字
String apn = cursor.getString(APN_INDEX);//APN名字
String key = cursor.getString(ID_INDEX);//这个是数据库里每项记录的主键ID
String type = cursor.getString(TYPES_INDEX);//类型
String mvnoType = cursor.getString(MVNO_TYPE_INDEX);//MVNO 类型
String mvnoMatchData = cursor.getString(MVNO_MATCH_DATA_INDEX);//MVNO 数据
ApnPreference pref = new ApnPreference(getPrefContext());//创建ApnPreference对象保存APN数据
pref.setKey(key);
pref.setTitle(name);
pref.setSummary(apn);
pref.setPersistent(false);
pref.setOnPreferenceChangeListener(this);
pref.setSubId(subId);
boolean selectable = ((type == null) || !type.equals("mms"));//将mms类型的APN和非mms类型的APN分开处理
pref.setSelectable(selectable);
if (selectable) {
if ((mSelectedKey != null) && mSelectedKey.equals(key)) {
pref.setChecked();
}
//addApnToList函数负责将不同类型的APN放进前面创建的四个不同ArrayList对象中。
addApnToList(pref, mnoApnList, mvnoApnList, r, mvnoType, mvnoMatchData);
} else {
addApnToList(pref, mnoMmsApnList, mvnoMmsApnList, r, mvnoType, mvnoMatchData);
}
cursor.moveToNext();
}
cursor.close();
//如果有MVNO类型的APN, mvnoApnList和mnoMmsApnList就分别重新指向保存了MVNO类型APN的mvnoApnList
//和mvnoMmsApnList, 结果是mvnoApnList和mnoMmsApnList之前存储的APN记录不会被显示出来。
if (!mvnoApnList.isEmpty()) {
mnoApnList = mvnoApnList;
mnoMmsApnList = mvnoMmsApnList;
// Also save the mvno info
}
for (Preference preference : mnoApnList) {//添加显示mnoApnList中的APN记录
apnList.addPreference(preference);
}
for (Preference preference : mnoMmsApnList) {//添加显示mnoMmsApnList中的APN记录
apnList.addPreference(preference);
}
}
}
addApnToList函数负责将不同类型的APN放进前面创建的四个不同ArrayList
private void addApnToList(ApnPreference pref, ArrayList mnoList,
ArrayList mvnoList, IccRecords r, String mvnoType,
String mvnoMatchData) {
if (r != null && !TextUtils.isEmpty(mvnoType) && !TextUtils.isEmpty(mvnoMatchData)) {
if (ApnSetting.mvnoMatches(r, mvnoType, mvnoMatchData)) {
mvnoList.add(pref);
// Since adding to mvno list, save mvno info
mMvnoType = mvnoType;
mMvnoMatchData = mvnoMatchData;
}
} else {
mnoList.add(pref);
}
}
添加,编辑APN都是从ApnSettings跳转到ApnEditor,并由ApnEditor.validateAndSave方法将数据写入数据库。
当添加新APN进入ApnEditor时,ApnEditor.onCreate方法根据intent的action(ACTION_INSERT),先在数据库中插入了一条空数据,并将包含数据记录ID的uri保存在mUri中,以便在ApnEditor.validateAndSave中使用。所以ApnEditor.validateAndSave将数据写入数据库时可以使用update方法,这样也同时满足了编辑操作的需求。
“Restore to default”是什么实现的?
当点击”Restore to defualt”菜单时,ApnSettings.onOptionsItemSelected 方法会调用
ApnSettings.restoreDefaultApn方法。
private boolean restoreDefaultApn() {
showDialog(DIALOG_RESTORE_DEFAULTAPN);//显示一个友好的提示框
mRestoreDefaultApnMode = true;//标识变量
if (mRestoreApnUiHandler == null) {
mRestoreApnUiHandler = new RestoreApnUiHandler();//这个是主线程中的handler
}
if (mRestoreApnProcessHandler == null ||
mRestoreDefaultApnThread == null) {
mRestoreDefaultApnThread = new HandlerThread(//创建了一个HandlerThread
"Restore default APN Handler: Process Thread");
mRestoreDefaultApnThread.start();//启动新创建的HandlerThread
mRestoreApnProcessHandler = new RestoreApnProcessHandler(
mRestoreDefaultApnThread.getLooper(), mRestoreApnUiHandler);//使用HandlerThread的looper创建了一个handler, mRestoreApnUiHandler用于通知UI。
}
mRestoreApnProcessHandler
.sendEmptyMessage(EVENT_RESTORE_DEFAULTAPN_START);// 发送EVENT_RESTORE_DEFAULTAPN_START,开始恢复数据。
return true;
}
RestoreApnProcessHandler会处理EVENT_RESTORE_DEFAULTAPN_START,操作比较简单,只是通过ContentResolver调用delete去删除数据。下面我们看看Uri的内容,已经TelephonyProvider如何做的处理。
Uri处理是通过getUriForCurrSubId(Uri uri)方法,该方法是将SubId和传入的Uri拼接到一块; 而传入的参数是ApnSettings定义好的常量,如下:
public static final String RESTORE_CARRIERS_URI =
"content://telephony/carriers/restore";
...
private static final Uri DEFAULTAPN_URI = Uri.parse(RESTORE_CARRIERS_URI);
所以完整的Uri是”content://telephony/carriers/restore/subId/*“。
下面的TelephonyProvider中Macher的定义:
static {
s_urlMatcher.addURI("telephony", "carriers", URL_TELEPHONY);
s_urlMatcher.addURI("telephony", "carriers/current", URL_CURRENT);
----------省略------------
/*restore 对应的Uri*/
s_urlMatcher.addURI("telephony", "carriers/restore/subId/*", URL_RESTOREAPN_USING_SUBID);
...
s_currentSetMap = new ContentValues(1);
s_currentSetMap.put(CURRENT, "1");
}
从上面代码中可以看出,ApnSettings中的Uri对应的是URL_RESTOREAPN_USING_SUBID,下面截取了delete方法中的相关code:
@Override
public synchronized int delete(Uri url, String where, String[] whereArgs)
{
int count = 0;
int subId = SubscriptionManager.getDefaultSubscriptionId();
String userOrCarrierEdited = ") and (" +
EDITED + "=" + USER_EDITED + " or " +
EDITED + "=" + CARRIER_EDITED + ")";
String notUserOrCarrierEdited = ") and (" +
EDITED + "!=" + USER_EDITED + " and " +
EDITED + "!=" + CARRIER_EDITED + ")";
ContentValues cv = new ContentValues();
cv.put(EDITED, USER_DELETED);
checkPermission();
SQLiteDatabase db = getWritableDatabase();
int match = s_urlMatcher.match(url);
switch (match)
{
----------省略------------
case URL_RESTOREAPN_USING_SUBID: {
String subIdString = url.getLastPathSegment();//解析subId数据
try {
subId = Integer.parseInt(subIdString);//转换成int类型
} catch (NumberFormatException e) {
loge("NumberFormatException" + e);
throw new IllegalArgumentException("Invalid subId " + url);
}
if (DBG) log("subIdString = " + subIdString + " subId = " + subId);
// FIXME use subId in query
}
case URL_RESTOREAPN: {
count = 1;
restoreDefaultAPN(subId);//调用restoreDefaultAPN(int subId)方法完成restore操作
break;
}
----------省略------------
}
----------省略------------
}
restoreDefaultAPN方法会直接删除carrier表和 shared preferences 中的数据,然后调用DatabaseHelper.initDatabase方法重新初始数据。也就是说APN相关数据全部删除,重新从配置文件中初始化。
private void restoreDefaultAPN(int subId) {
SQLiteDatabase db = getWritableDatabase();
try {
db.delete(CARRIERS_TABLE, null, null);//直接删除carrier表中的全部数据
} catch (SQLException e) {
loge("got exception when deleting to restore: " + e);
}
//下面的code删除了shared preferences文件
// delete preferred apn ids and preferred apns (both stored in diff SharedPref) for all
// subIds
SharedPreferences spApnId = getContext().getSharedPreferences(PREF_FILE_APN,
Context.MODE_PRIVATE);
SharedPreferences.Editor editorApnId = spApnId.edit();
editorApnId.clear();
editorApnId.apply();
SharedPreferences spApn = getContext().getSharedPreferences(PREF_FILE_FULL_APN,
Context.MODE_PRIVATE);
SharedPreferences.Editor editorApn = spApn.edit();
editorApn.clear();
editorApn.apply();
initDatabaseWithDatabaseHelper(db);//该方法会调用DatabaseHelper.initDatabase方法。
}