终端中有一个apns-config.xml文件,负责定义各个运营商规定的默认APN参数。
开机后,终端启动Phone进程时,会加载运行在Phone进程中的TelephonyProvider。
TelephonyProvider负责解析apns-config.xml文件,将其中定义的APN参数写入到数据库中。
Android 7.0中这一部分的流程,与Android 6.0基本类似,可以参考Android6.0 APN。
在这边博客中我们重点看看:
1、插卡后,手机选择可以使用的APN的流程;
2、终端UI界面,修改(新建)APN的流程;
3、Android中APN配置相关的漏洞——在某些场景下,数据连接断开失败。
一、插卡后APN选择流程
在这篇博客中,我们不分析终端完整的检卡流程,仅关注与APN相关的部分。
首先来看一下DcTracker的构造函数:
public DcTracker(Phone phone) {
.......
//每个Phone对象有自己DcTracker
//每个DcTracker加载各自卡可用的APN
mPhone = phone;
.......
//1、监听卡载入
mUiccController = UiccController.getInstance();
mUiccController.registerForIccChanged(this, DctConstants.EVENT_ICC_CHANGED, null);
.......
//2、监听卡信息变化
mSubscriptionManager = SubscriptionManager.from(mPhone.getContext());
mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener);
.......
//监听APN数据库变化
mApnObserver = new ApnChangeObserver();
phone.getContext().getContentResolver().registerContentObserver(
Telephony.Carriers.CONTENT_URI, true, mApnObserver);
.............
//初始化不同APN类型对应的网络能力,后文介绍
initApnContexts();
.............
// Add Emergency APN to APN setting list by default to support EPDN in sim absent cases
initEmergencyApnSetting();
addEmergencyApnSetting();
...............
}
在这一部分,我们先研究一下卡相关的内容。
APN数据库变化触发的流程,放在下一部分介绍。
1、EVENT_ICC_CHANGED
根据DcTracker的构造函数,我们知道DcTracker注册成为UiccController的观察者,监听Icc Changed事件。
当UiccController通知DcTracker时,将触发DcTracker发送DctConstants.EVENT_ICC_CHANGED给自己处理。
在DcTracker的handleMessage函数中:
public void handleMessage (Message msg) {
.........
case DctConstants.EVENT_ICC_CHANGED: {
onUpdateIcc();
break;
}
.........
}
跟进一下onUpdateIcc函数:
private void onUpdateIcc() {
..........
//利用UiccController得到当前Phone对应的iccRecord
IccRecords newIccRecords = getUiccRecords(UiccController.APP_FAM_3GPP);
//旧有的IccRecord
IccRecords r = mIccRecords.get();
if (r != newIccRecords) {
//移除对旧有信息的记录
if (r != null) {
log("Removing stale icc objects.");
r.unregisterForRecordsLoaded(this);
mIccRecords.set(null);
}
if (newIccRecords != null) {
if (SubscriptionManager.isValidSubscriptionId(mPhone.getSubId())) {
log("New records found.");
mIccRecords.set(newIccRecords);
//向IccRecord注册,观察卡信息是否载入完成
//收到通知后,DcTracker将发送EVENT_RECORDS_LOADED信息给自己处理
newIccRecords.registerForRecordsLoaded(
this, DctConstants.EVENT_RECORDS_LOADED, null);
//这里应该是7.0新加入的,将SIM卡置为SIM_PROVISIONED状态,表示卡是激活的
SubscriptionController.getInstance().setSimProvisioningStatus(
SubscriptionManager.SIM_PROVISIONED, mPhone.getSubId());
}
} else {
//处理卡被移除的情况
onSimNotReady();
}
}
}
我们再来看看handleMessage中对EVENT_RECORDS_LOADED函数的处理:
public void handleMessage (Message msg) {
.........
case DctConstants.EVENT_RECORDS_LOADED:
int subId = mPhone.getSubId();
if (SubscriptionManager.isValidSubscriptionId(subId)) {
//卡信息载入完成后,调用onRecordsLoadedOrSubIdChanged函数进行处理
onRecordsLoadedOrSubIdChanged();
} else {
log("Ignoring EVENT_RECORDS_LOADED as subId is not valid: " + subId);
}
break;
.........
}
从上面的代码可以看出,卡信息加载完成后,DcTracker将调用onRecordsLoadedOrSubIdChanged函数进行处理。
这个函数等下再做进一步介绍。
2、Subscriptions Changed
从DcTracker的构造函数,可以看出DcTracker还向SubscriptionManager注册了一个Listener。
当SubscriptionManager发现卡信息变化时,也会通过该Listener进行回调。
看看这个Listener的实现:
private final OnSubscriptionsChangedListener mOnSubscriptionsChangedListener =
new OnSubscriptionsChangedListener() {
public final AtomicInteger mPreviousSubId =
new AtomicInteger(SubscriptionManager.INVALID_SUBSCRIPTION_ID);
public void onSubscriptionsChanged() {
.............
int subId = mPhone.getSubId();
if (SubscriptionManager.isValidSubscriptionId(subId)) {
//监听一些数据库变化
registerSettingsObserver();
/* check if sim is un-provisioned */
//7.0新增的,主要是根据卡对应的SubscriptionInfo判断卡是否激活
//如果卡从激活变成未激活,就要断开数据连接(如果存在),并进行通知
applyUnProvisionedSimDetected();
}
//subId发生变化,说明卡变化了
if (mPreviousSubId.getAndSet(subId) != subId &&
SubscriptionManager.isValidSubscriptionId(subId)) {
onRecordsLoadedOrSubIdChanged();
}
}
};
从Listener的代码不难看出,当SubscriptionManager回调其接口onSubscriptionsChanged时,若检测到卡发生变化,也会调用onRecordsLoadedOrSubIdChanged函数。
现在我们就可以明白onRecordsLoadedOrSubIdChanged函数命名的由来:不论是检测到卡信息载入完成,还是卡的SubId发生变化,该函数均会被调用。
3、onRecordsLoadedOrSubIdChanged
现在我们看看onRecordsLoadedOrSubIdChanged函数:
private void onRecordsLoadedOrSubIdChanged() {
..............
//1、创建当前卡可用的APN
createAllApnList();
//2、设置初始使用的APN
setInitialAttachApn();
if (mPhone.mCi.getRadioState().isOn()) {
if (DBG) log("onRecordsLoadedOrSubIdChanged: notifying data availability");
notifyOffApnsOfAvailability(Phone.REASON_SIM_LOADED);
}
//卡变化也会触发拨号流程;不过若此时数据开关未开,那么拨号是不会成功的
setupDataOnConnectableApns(Phone.REASON_SIM_LOADED);
}
从上面的代码可以看出,插卡或卡发生变化后,就要创建当前卡可用的APN,同时设置初始时使用的APN。
接下来,我们分别看看这两个流程。
3.1、createAllApnList
首先看看创建卡对应APN的过程:
private void createAllApnList() {
//表示mvno是否匹配
//mvno也是APN的一种属性,代表该APN适用于虚拟运营商,目前用的比较少
mMvnoMatched = false;
//用于保存结果
mAllApnSettings = new ArrayList<ApnSetting>();
//得到当前卡的信息
IccRecords r = mIccRecords.get();
//得到卡对应的MCC/MNC
String operator = (r != null) ? r.getOperatorNumeric() : "";
if (operator != null) {
//构造SQL语句
String selection = "numeric = '" + operator + "'";
String orderBy = "_id";
...............
//查询MCC/MNC对应的APN
Cursor cursor = mPhone.getContext().getContentResolver().query(
Telephony.Carriers.CONTENT_URI, null, selection, null, orderBy);
if (cursor != null) {
if (cursor.getCount() > 0) {
//1、利用数据创建APN
mAllApnSettings = createApnList(cursor);
}
cursor.close();
}
}
//2、添加emergencyApnSettings
addEmergencyApnSetting();
//3、去除重复的APN
dedupeApnSettings();
if (mAllApnSettings.isEmpty()) {
mPreferredApn = null;
} else {
//4、得到用户偏爱的APN (用户在UI界面主动选择的)
mPreferredApn = getPreferredApn();
if (mPreferredApn != null && !mPreferredApn.numeric.equals(operator) {
mPreferredApn = null;
//用户偏爱的与当前卡不匹配,删除数据库中对应信息
setPreferredApn(-1);
}
}
//5、在需要的情况下,构造APN文件发送给modem
setDataProfilesAsNeeded();
}
以上是创建卡对应APN的整个过程,细节还是挺多的。
不过主干的思路还是很清晰:TelephonyProvider在初始时,加载了apns-conf.xml中所有信息;同时,如果用户自己新建过APN,那么对应的信息也会存入到数据库中。
此时,就是根据MCC/MNC取出所有对应的APN信息。
3.1.1 createApnList
private ArrayList<ApnSetting> createApnList(Cursor cursor) {
//Framework是用的APN数据结构为ApnSetting
ArrayList<ApnSetting> mnoApns = new ArrayList<ApnSetting>();
ArrayList<ApnSetting> mvnoApns = new ArrayList<ApnSetting>();
IccRecords r = mIccRecords.get();
if (cursor.moveToFirst()) {
do {
//利用数据创建ApnSetting
ApnSetting apn = makeApnSetting(cursor);
if (apn == null) {
continue;
}
//判断APN是否有mvnoType和mvnoMatchData
if (apn.hasMvnoParams()) {
//卡与mvno相关的信息匹配
if (r != null && ApnSetting.mvnoMatches(r, apn.mvnoType, apn.mvnoMatchData)) {
mvnoApns.add(apn);
}
} else {
mnoApns.add(apn);
}
} while (cursor.moveToNext());
}
//从下面结果的选择的代码来看
//一张卡要么支持普通运营商,要么支持虚拟运营商
ArrayList<ApnSetting> result;
if (mvnoApns.isEmpty()) {
result = mnoApns;
mMvnoMatched = false;
} else {
result = mvnoApns;
//虚拟运营商时,mMvnoMatched置为true
mMvnoMatched = true;
}
}
以上过程还是比较简单的,我们跟进一下makeApnSetting:
private ApnSetting makeApnSetting(Cursor cursor) {
String[] types = parseTypes(
cursor.getString(cursor.getColumnIndexOrThrow(Telephony.Carriers.TYPE)));
//从数据库中读取各种信息,共同构造ApnSetting
ApnSetting apn = new ApnSetting(
cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID)),
................);
return apn;
}
这里的代码没什么疑点,就是利用数据库的信息,调用ApnSetting的构造函数。
我们进入parseTypes看看:
private String[] parseTypes(String types) {
String[] result;
// If unset, set to DEFAULT.
if (types == null || types.equals("")) {
result = new String[1];
result[0] = PhoneConstants.APN_TYPE_ALL;
} else {
//一个APN可以包含多个type
result = types.split(",");
}
return result;
}
这段代码是解析APN的type字段。APN的type域,决定了它提供的网络能力。
关于type,我们可以参考前面提到的DcTracker构造函数中的initApnContexts函数:
private void initApnContexts() {
..........
// Load device network attributes from resources
String[] networkConfigStrings = mPhone.getContext().getResources().getStringArray(
com.android.internal.R.array.networkAttributes);
for (String networkConfigString : networkConfigStrings) {
NetworkConfig networkConfig = new NetworkConfig(networkConfigString);
ApnContext apnContext = null;
switch (networkConfig.type) {
case ConnectivityManager.TYPE_MOBILE:
//ApnContext是拨号时使用的数据结构
//这里创建ApnContext时,将Network Config与APN type关联起来了
apnContext = addApnContext(PhoneConstants.APN_TYPE_DEFAULT, networkConfig);
break;
case ConnectivityManager.TYPE_MOBILE_MMS:
apnContext = addApnContext(PhoneConstants.APN_TYPE_MMS, networkConfig);
break;
................
}
}
................
}
结合parseTypes和initApnContexts,我们就能知道APN type对应的具体网络能力。
例如:APN type包含default时,利用这个APN建立的网络就具有Mobile能力,即能够用数据网络访问Internet;
当APN type包含mms时,利用这个APN建立的网络就具有发送彩信的能力。
从parseTypes函数可以看出,当APN的type为空时,即没有配置时,APN的type被定义为APN_TYPE_ALL。
利用APN_TYPE_ALL建立的网络,将具有全部的网络能力。
正常情况下,这种设计是合理的:
运营商会不同的服务定义不同的网络,于是通过APN的type域进行区分;
但是,有的运营商在某些地区会用同一个网络支持所有的功能(例如在非洲的一些国家),此时将APN的type域写成”default, mms, supl, dun, hipri, fota, ims…….”是件繁琐的事,
于是,就规定APN的type域为”“时,可以支持所有网络能力。
然而,这种设计成为了Android的一个漏洞,在某些场景下,将带来数据连接无法断开的问题。
关于这个问题的成因,我们在最后分析。
3.1.2 addEmergencyApnSetting
接下来,我们看看addEmergencyApnSetting中的内容:
private void addEmergencyApnSetting() {
if(mEmergencyApn != null) {
if(mAllApnSettings == null) {
mAllApnSettings = new ArrayList<ApnSetting>();
} else {
boolean hasEmergencyApn = false;
for (ApnSetting apn : mAllApnSettings) {
if (ArrayUtils.contains(apn.types, PhoneConstants.APN_TYPE_EMERGENCY)) {
hasEmergencyApn = true;
break;
}
}
if(hasEmergencyApn == false) {
//将mEmergencyApn插入到当前卡可用的Apn List中
mAllApnSettings.add(mEmergencyApn);
} else {
log("addEmergencyApnSetting - E-APN setting is already present");
}
}
}
}
插入卡后,我们从数据库中取出与该卡MCC/MNC匹配的数据,构建对应的ApnSetting。
但是每个卡还需要支持紧急拨号对应网络,因此在加载完数据库中匹配数据后,将mEmergencyApn也写入到mAllApnSettings中。
mEmergencyApn在DcTracker的构造函数中调用initEmergencyApnSetting得到:
private