每篇一格言:
人生就像滚雪球,关键是要找到足够湿的雪,和足够长的坡。
——沃伦巴菲特
In the GPRS backbone, an Access Point Name (APN) is a reference to a GGSN. To support inter-PLMN roaming, the internal GPRS DNS functionality is used to translate the APN into the IP address of the GGSN.
——3gpp 23.003
从定义可看出,APN是GGSN的引用,被internal GPRS DNS转换为GGSN的IP地址。
那么GGSN是什么,又是做什么的?
GGSN: Gateway GPRS Support Node, 网关GPRS支持节点
GGSN主要起网关作用,所扮演的角色:
对内:网络传输; (网络接入控制,分组数据的过滤)
对外:路由器(路由选择和分组的转发,IP地址分配)
一个典型的APN有如下参数(名称,MCCMNC,接入点,类型)
mnc=“07” //MNC
apn=“cmnet” //接入点
type=“default,supl,net” //类型
/>
其他更多参数:
Note :每个有数据业务的运营商都会设定自己的APN,APN可能是多条,包括3G使用、4G使用,NET和WAP,不同APN的使用范围和收费会有差别。
以XML的格式存储。
文件名:
Apns-conf.xml
源码文件路径:
MTK平台(通常):alps\mediatek\frameworks\base\telephony\etc
高通平台(通常):android\vendor\qcom\proprietary\telephony-apps\etc
UE文件路径:
system/etc/ Apns-conf.xml
当UE开机后,读取XML并写入到database中。
database中table名:
content://telephony/carriers
注:有些平台为了适配dual SIM,可能会加上sub1/sub2等。
加载到database:
TelephonyProvider读取XML并在database中插入apn的table。
加载到UI菜单:
根据SIM卡的MCCMNC,去匹配database中同样MCCMNC的APN项,并将匹配到的APN填写到菜单列表。
加载到PDP请求:
由DCtracker负责创建/更新waiting APN list,供PDP选用。
虚拟运营商(MVNO)没有自营网段,使用了主运营商的网段,因而和主运营商有相同的MCCMNC。为了能够与主运营商区分,虚拟运营商的APN还包含了MVNO参数。MVNO参数分为SPN/PNN/IMSI/GID1,是从SIM卡对应栏位读取的值,目的是从该值中判断该SIM卡是否属于MVNO。
在加载MVNO SIM卡的APN时,会同时去匹配MCCMNC和MVNO参数。
在上一章中我们讲到,APN的原始参数存放在XML中;
而UE是通过query database的方式使用APN;
因而必然需要将APN从XML录入到database,这一步在telephonyprovider中实现。
为了更清晰的理解实现思路,避免过多的源码干扰阅读节奏,接下来的分析中涉及到的源码部分,都以伪代码的形式给出,详细的代码以附录形式给出。
分析telephonyprovider中的initDatabase方法:
private void initDatabase(SQLiteDatabase db) {
1.打开APN xml文件etc/apns-conf.xml
2. 获得文件句柄后,使用FileReader得到文件字符流
3. 检查APN version一致性
4.加载XMl中的数据到database,具体见loadApns方法
}
对第3点:“检查APN version一致性” 的说明:
随着OS升级,APN字段也在更新,因此检查APN version的目的是为了避免XML与OS不兼容。若version不一致,抛出异常:
// Sanity check. Force internal version and confidential versions to agree
int confversion = Integer.parseInt(confparser.getAttributeValue(null, "version"));
if (publicversion != confversion) {
throw new IllegalStateException("Internal APNS file version doesn't match "
+ confFile.getAbsolutePath());
}
分析loadApns方法
private void loadApns(SQLiteDatabase db, XmlPullParser parser) {
1。每次读取parser中的一个element,也就是一条APN数据
2.通过getrow方法将APN转换为Contentvalues;
3.最后通过insertAddingDefaults将键值写入database:
}
写入的table名是CARRIERS_TABLE,该table由createCarriersTable方法创建。
该table url是:content://telephony/carriers/
table 创建时机:
telephonyprovider的内部类DatabaseHelper在oncreate时创建APN table。
DatabaseHelper类负责APN database的增删改查。
设备插入SIM后,设置菜单中会显示该SIM卡对应的APN菜单。
这部分在ApnSettings.java (android\packages\apps\settings\src\com\android\settings) 中的fillList方法实现的。该方法主要根据SIM卡的mccmnc去query db并将APN填入菜单中。感兴趣可自行查看,不再具体讨论。
对于MVNO SIM卡,除了mccmnc还需根据MVNO type和MVNO value过滤。详细见mvnoMatches方法。
备注1.
SIM卡的mccmnc获取的是PROPERTY_ICC_OPERATOR_NUMERIC的值,而PROPERTY_ICC_OPERATOR_NUMERIC是在SIM loaded后根据IMSI得到。(取前5位or前6位)
备注2.
mvno type举例:SPN类型,通过getServiceProviderName获取,其值来自SIM卡中的EF_SPN:分为SIM (EF_SPN=0x6F46)或 RUIM (EF_RUIM_SPN=0x6F41)。
有关SIM卡加载的更多内容,请移步这里:
Android:USIM软件架构与加载流程
UE插入SIM卡后,telephony根据SIM卡的mccmnc,先创建一个包含该SIM卡所有APN的列表(AllApnList)。
这一步在DcTracker中实现。Call flow见下图:
下面列出了createAllApnList的实现步骤:
protected void createAllApnList() {
1。根据SIM卡mccmnc,创建APN list
2。设置preferred APN
3。Set DataProfile in modem
}
有了AllApnList,在setup data时,根据数据业务类型(上网/彩信/…),从all apn list中筛选出“waiting apn list”,用于建立PDP.
private ArrayList buildWaitingApns(String requestedApnType, int radioTech) {
//根据requestedApnType和radioTech筛选waiting apn
}
modem中同样存储了APN,详细内容可参考我的另一篇文章:
Modem2G/3G/4G/5G:APN:使用AT+CGDCONT命令设置modem默认APN(CID1)
你可能想知道更多详细内容,下面源码以附录形式给出。
TelephonyProvider.java (amss\linux\android\packages\providers\telephonyprovider\src\com\android\providers\telephony)
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);
int publicversion = -1;
try {
XmlUtils.beginDocument(parser, "apns");
publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
loadApns(db, parser);
} catch (Exception e) {
loge("Got exception while loading APN database." + e);
} finally {
parser.close();
}
// Read external APNS data (partner-provided)
XmlPullParser confparser = null;
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);
} 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");
}
// Delete USER_DELETED
db.delete(CARRIERS_TABLE, IS_USER_DELETED + " or " + IS_CARRIER_DELETED, null);
// 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);
}
private void loadApns(SQLiteDatabase db, XmlPullParser parser) {
if (parser != null) {
try {
db.beginTransaction();
XmlUtils.nextElement(parser);
while (parser.getEventType() != XmlPullParser.END_DOCUMENT) {
ContentValues row = getRow(parser);
if (row == null) {
throw new XmlPullParserException("Expected 'apn' tag", parser, null);
}
insertAddingDefaults(db, row);
XmlUtils.nextElement(parser);
}
db.setTransactionSuccessful();
} catch (XmlPullParserException e) {
loge("Got XmlPullParserException while loading apns." + e);
} catch (IOException e) {
loge("Got IOException while loading apns." + e);
} catch (SQLException e) {
loge("Got SQLException while loading apns." + e);
} finally {
db.endTransaction();
}
}
}
ApnSettings.java (amss\linux\android\packages\apps\settings\src\com\android\settings)
private void fillList() {
final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
final int subId = mSubscriptionInfo != null ? mSubscriptionInfo.getSubscriptionId()
: SubscriptionManager.INVALID_SUBSCRIPTION_ID;
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");
if (mHideImsApn) {
where.append(" AND NOT (type='ims')");
}
appendFilter(where);
Log.d(TAG, "where = " + where.toString());
Cursor cursor = getContentResolver().query(Telephony.Carriers.CONTENT_URI, new String[] {
"_id", "name", "apn", "type", "mvno_type", "mvno_match_data", "bearer", "bearer_bitmask"}, where.toString(),
null, Telephony.Carriers.DEFAULT_SORT_ORDER);
if (cursor != null) {
IccRecords r = null;
if (mUiccController != null && mSubscriptionInfo != null) {
r = mUiccController.getIccRecords(
SubscriptionManager.getPhoneId(subId), UiccController.APP_FAM_3GPP);
}
PreferenceGroup apnList = (PreferenceGroup) findPreference("apn_list");
apnList.removeAll();
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);
String apn = cursor.getString(APN_INDEX);
String key = cursor.getString(ID_INDEX);
String type = cursor.getString(TYPES_INDEX);
String mvnoType = cursor.getString(MVNO_TYPE_INDEX);
String mvnoMatchData = cursor.getString(MVNO_MATCH_DATA_INDEX);
//Special requirement of some operators, need change APN name follow language.
String localizedName = Utils.getLocalizedName(getActivity(), cursor, NAME_INDEX);
if (!TextUtils.isEmpty(localizedName)) {
name = localizedName;
}
int bearer = cursor.getInt(BEARER_INDEX);
int bearerBitMask = cursor.getInt(BEARER_BITMASK_INDEX);
int fullBearer = ServiceState.getBitmaskForTech(bearer) | bearerBitMask;
int radioTech = networkTypeToRilRidioTechnology(TelephonyManager.getDefault()
.getDataNetworkType(subId));
if (!ServiceState.bitmaskHasTech(fullBearer, radioTech)
&& (bearer != 0 || bearerBitMask != 0)) {
// In OOS, show APN with bearer as default
if ((radioTech != ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN) || (bearer == 0
&& radioTech == ServiceState.RIL_RADIO_TECHNOLOGY_UNKNOWN)) {
cursor.moveToNext();
continue;
}
}
ApnPreference pref = new ApnPreference(getPrefContext());
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"));
pref.setSelectable(selectable);
if (selectable) {
if ((mSelectedKey != null) && mSelectedKey.equals(key)) {
pref.setChecked();
}
addApnToList(pref, mnoApnList, mvnoApnList, r, mvnoType, mvnoMatchData);
} else {
addApnToList(pref, mnoMmsApnList, mvnoMmsApnList, r, mvnoType, mvnoMatchData);
}
cursor.moveToNext();
}
cursor.close();
if (!mvnoApnList.isEmpty()) {
mnoApnList = mvnoApnList;
mnoMmsApnList = mvnoMmsApnList;
// Also save the mvno info
}
for (Preference preference : mnoApnList) {
apnList.addPreference(preference);
}
for (Preference preference : mnoMmsApnList) {
apnList.addPreference(preference);
}
}
}
MVNO的处理:
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);
}
}
DcTracker.java
createAllApn:
protected void createAllApnList() {
mMvnoMatched = false;
mAllApnSettings = new ArrayList<>();
IccRecords r = mIccRecords.get();
String operator = mPhone.getOperatorNumeric();
if (operator != null) {
String selection = Telephony.Carriers.NUMERIC + " = '" + operator + "'";
// query only enabled apn.
// carrier_enabled : 1 means enabled apn, 0 disabled apn.
// selection += " and carrier_enabled = 1";
if (DBG) log("createAllApnList: selection=" + selection);
// ORDER BY Telephony.Carriers._ID ("_id")
Cursor cursor = mPhone.getContext().getContentResolver().query(
Telephony.Carriers.CONTENT_URI, null, selection, null, Telephony.Carriers._ID);
if (cursor != null) {
if (cursor.getCount() > 0) {
mAllApnSettings = createApnList(cursor);
}
cursor.close();
}
}
addEmergencyApnSetting();
dedupeApnSettings();
if (mAllApnSettings.isEmpty()) {
if (DBG) log("createAllApnList: No APN found for carrier: " + operator);
mPreferredApn = null;
// TODO: What is the right behavior?
//notifyNoData(DataConnection.FailCause.MISSING_UNKNOWN_APN);
} else {
mPreferredApn = getPreferredApn();
if (mPreferredApn != null && !mPreferredApn.numeric.equals(operator)) {
mPreferredApn = null;
setPreferredApn(-1);
}
if (DBG) log("createAllApnList: mPreferredApn=" + mPreferredApn);
}
if (DBG) log("createAllApnList: X mAllApnSettings=" + mAllApnSettings);
setDataProfilesAsNeeded();
}
筛选候选apn用于PDP
private ArrayList buildWaitingApns(String requestedApnType, int radioTech) {
if (DBG) log("buildWaitingApns: E requestedApnType=" + requestedApnType);
ArrayList apnList = new ArrayList();
if (requestedApnType.equals(PhoneConstants.APN_TYPE_DUN)) {
ApnSetting dun = fetchDunApn();
if (dun != null) {
apnList.add(dun);
if (DBG) log("buildWaitingApns: X added APN_TYPE_DUN apnList=" + apnList);
return apnList;
}
}
String operator = mPhone.getOperatorNumeric();
// This is a workaround for a bug (7305641) where we don't failover to other
// suitable APNs if our preferred APN fails. On prepaid ATT sims we need to
// failover to a provisioning APN, but once we've used their default data
// connection we are locked to it for life. This change allows ATT devices
// to say they don't want to use preferred at all.
boolean usePreferred = true;
try {
usePreferred = ! mPhone.getContext().getResources().getBoolean(com.android.
internal.R.bool.config_dontPreferApn);
} catch (Resources.NotFoundException e) {
if (DBG) log("buildWaitingApns: usePreferred NotFoundException set to true");
usePreferred = true;
}
if (usePreferred) {
mPreferredApn = getPreferredApn();
}
if (DBG) {
log("buildWaitingApns: usePreferred=" + usePreferred
+ " canSetPreferApn=" + mCanSetPreferApn
+ " mPreferredApn=" + mPreferredApn
+ " operator=" + operator + " radioTech=" + radioTech
+ " IccRecords r=" + mIccRecords);
}
if (usePreferred && mCanSetPreferApn && mPreferredApn != null &&
mPreferredApn.canHandleType(requestedApnType)) {
if (DBG) {
log("buildWaitingApns: Preferred APN:" + operator + ":"
+ mPreferredApn.numeric + ":" + mPreferredApn);
}
if (mPreferredApn.numeric.equals(operator)) {
if (ServiceState.bitmaskHasTech(mPreferredApn.bearerBitmask, radioTech)) {
apnList.add(mPreferredApn);
if (DBG) log("buildWaitingApns: X added preferred apnList=" + apnList);
return apnList;
} else {
if (DBG) log("buildWaitingApns: no preferred APN");
setPreferredApn(-1);
mPreferredApn = null;
}
} else {
if (DBG) log("buildWaitingApns: no preferred APN");
setPreferredApn(-1);
mPreferredApn = null;
}
}
if (mAllApnSettings != null) {
if (DBG) log("buildWaitingApns: mAllApnSettings=" + mAllApnSettings);
for (ApnSetting apn : mAllApnSettings) {
if (apn.canHandleType(requestedApnType)) {
if (ServiceState.bitmaskHasTech(apn.bearerBitmask, radioTech)) {
if (DBG) log("buildWaitingApns: adding apn=" + apn);
apnList.add(apn);
} else {
if (DBG) {
log("buildWaitingApns: bearerBitmask:" + apn.bearerBitmask + " does " +
"not include radioTech:" + radioTech);
}
}
} else if (DBG) {
log("buildWaitingApns: couldn't handle requested ApnType="
+ requestedApnType);
}
}
} else {
loge("mAllApnSettings is null!");
}
if (DBG) log("buildWaitingApns: " + apnList.size() + " APNs in the list: " + apnList);
return apnList;
}
相关章节:
全面&系统的解析android RSSI信号显示与刷新,总篇
本文为原创文章。看完点赞,每天进步一点点~