终端中有一个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) { ....... mPhone = phone; ....... mUiccController = UiccController.getInstance(); mUiccController.registerForIccChanged(this, DctConstants.EVENT_ICC_CHANGED, null); ....... mSubscriptionManager = SubscriptionManager.from(mPhone.getContext()); mSubscriptionManager.addOnSubscriptionsChangedListener(mOnSubscriptionsChangedListener); ....... mApnObserver = new ApnChangeObserver(); phone.getContext().getContentResolver().registerContentObserver( Telephony.Carriers.CONTENT_URI, true, mApnObserver); ............. initApnContexts(); ............. initEmergencyApnSetting(); addEmergencyApnSetting(); ...............}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
在这一部分,我们先研究一下卡相关的内容。
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() { .......... IccRecords newIccRecords = getUiccRecords(UiccController.APP_FAM_3GPP); 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); newIccRecords.registerForRecordsLoaded( this, DctConstants.EVENT_RECORDS_LOADED, null); SubscriptionController.getInstance().setSimProvisioningStatus( SubscriptionManager.SIM_PROVISIONED, mPhone.getSubId()); } } else { onSimNotReady(); } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
我们再来看看handleMessage中对EVENT_RECORDS_LOADED函数的处理:
public void handleMessage (Message msg) { ......... case DctConstants.EVENT_RECORDS_LOADED: int subId = mPhone.getSubId(); if (SubscriptionManager.isValidSubscriptionId(subId)) { 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(); applyUnProvisionedSimDetected(); } if (mPreviousSubId.getAndSet(subId) != subId && SubscriptionManager.isValidSubscriptionId(subId)) { onRecordsLoadedOrSubIdChanged(); } } };
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
从Listener的代码不难看出,当SubscriptionManager回调其接口onSubscriptionsChanged时,若检测到卡发生变化,也会调用onRecordsLoadedOrSubIdChanged函数。
现在我们就可以明白onRecordsLoadedOrSubIdChanged函数命名的由来:不论是检测到卡信息载入完成,还是卡的SubId发生变化,该函数均会被调用。
3、onRecordsLoadedOrSubIdChanged
现在我们看看onRecordsLoadedOrSubIdChanged函数:
private void onRecordsLoadedOrSubIdChanged() { .............. createAllApnList(); 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() { mMvnoMatched = false; mAllApnSettings = new ArrayList<ApnSetting>(); IccRecords r = mIccRecords.get(); String operator = (r != null) ? r.getOperatorNumeric() : ""; if (operator != null) { String selection = "numeric = '" + operator + "'"; String orderBy = "_id"; ............... Cursor cursor = mPhone.getContext().getContentResolver().query( Telephony.Carriers.CONTENT_URI, null, selection, null, orderBy); if (cursor != null) { if (cursor.getCount() > 0) { mAllApnSettings = createApnList(cursor); } cursor.close(); } } addEmergencyApnSetting(); dedupeApnSettings(); if (mAllApnSettings.isEmpty()) { mPreferredApn = null; } else { mPreferredApn = getPreferredApn(); if (mPreferredApn != null && !mPreferredApn.numeric.equals(operator) { mPreferredApn = null; setPreferredApn(-1); } } setDataProfilesAsNeeded();}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
以上是创建卡对应APN的整个过程,细节还是挺多的。
不过主干的思路还是很清晰:TelephonyProvider在初始时,加载了apns-conf.xml中所有信息;同时,如果用户自己新建过APN,那么对应的信息也会存入到数据库中。
此时,就是根据MCC/MNC取出所有对应的APN信息。
3.1.1 createApnList
private ArrayList<ApnSetting> createApnList(Cursor cursor) { ArrayList<ApnSetting> mnoApns = new ArrayList<ApnSetting>(); ArrayList<ApnSetting> mvnoApns = new ArrayList<ApnSetting>(); IccRecords r = mIccRecords.get(); if (cursor.moveToFirst()) { do { ApnSetting apn = makeApnSetting(cursor); if (apn == null) { continue; } if (apn.hasMvnoParams()) { 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; }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
以上过程还是比较简单的,我们跟进一下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 (types == null || types.equals("")) { result = new String[1]; result[0] = PhoneConstants.APN_TYPE_ALL; } else { result = types.split(","); } return result;}
这段代码是解析APN的type字段。APN的type域,决定了它提供的网络能力。
关于type,我们可以参考前面提到的DcTracker构造函数中的initApnContexts函数:
private void initApnContexts() { .......... 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 = addApnContext(PhoneConstants.APN_TYPE_DEFAULT, networkConfig); break; case ConnectivityManager.TYPE_MOBILE_MMS: apnContext = addApnContext(PhoneConstants.APN_TYPE_MMS, networkConfig); break; ................ } } ................}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
结合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) { mAllApnSettings.add(mEmergencyApn); } else { log("addEmergencyApnSetting - E-APN setting is already present"); } } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
插入卡后,我们从数据库中取出与该卡MCC/MNC匹配的数据,构建对应的ApnSetting。
但是每个卡还需要支持紧急拨号对应网络,因此在加载完数据库中匹配数据后,将mEmergencyApn也写入到mAllApnSettings中。
mEmergencyApn在DcTracker的构造函数中调用initEmergencyApnSetting得到:
private void initEmergencyApnSetting() { // Operator Numeric is not available when sim records are not loaded. // Query Telephony.db with APN type as EPDN request does not // require APN name, plmn and all operators support same APN config. // DB will contain only one entry for Emergency APN String selection = "type=\"emergency\""; Cursor cursor = mPhone.getContext().getContentResolver().query( Telephony.Carriers.CONTENT_URI, null, selection, null, null); if (cursor != null) { if (cursor.getCount() > 0) { if (cursor.moveToFirst()) { mEmergencyApn = makeApnSetting(cursor); } } cursor.close(); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
根据之前分析的内容,很容易看出mEmergencyApn也是通过查询数据库后构造的。
对于厂商而言,emergency是要保证写入到apns-conf.xml中的。
3.1.3 dedupeApnSettings
看过apns-conf.xml的人就知道,由于一些人为的原因,其中可能有很多APN是重复的。
dedupeApnSettings就是负责移除mAllApnSettings中重复的APN (或者说叫合并同类项)。
我们看看对应的代码:
private void dedupeApnSettings() { .......... int i = 0; while (i < mAllApnSettings.size() - 1) { ApnSetting first = mAllApnSettings.get(i); ApnSetting second = null; int j = i + 1; while (j < mAllApnSettings.size()) { second = mAllApnSettings.get(j); if (apnsSimilar(first, second)) { ApnSetting newApn = mergeApns(first, second); mAllApnSettings.set(i, newApn); first = newApn; mAllApnSettings.remove(j); } else { j++; } } i++; }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
上面的代码就是逐一比较当前卡可用的APN,找出其中相似的,并进行合并。
3.1.4 getPreferredApn
用户使用一张卡时,可能手动选择过使用的APN。
因此,当用户再次插拔卡后,getPreferredApn用于找出用户之前选择的APN。
我们看看对应的代码:
private ApnSetting getPreferredApn() { if (mAllApnSettings == null || mAllApnSettings.isEmpty()) { .............. return null; } String subId = Long.toString(mPhone.getSubId()); Uri uri = Uri.withAppendedPath(PREFERAPN_NO_UPDATE_URI_USING_SUBID, subId); Cursor cursor = mPhone.getContext().getContentResolver().query( uri, new String[] { "_id", "name", "apn" }, null, null, Telephony.Carriers.DEFAULT_SORT_ORDER); if (cursor != null) { mCanSetPreferApn = true; } else { mCanSetPreferApn = false; } if (mCanSetPreferApn && cursor.getCount() > 0) { int pos; cursor.moveToFirst(); pos = cursor.getInt(cursor.getColumnIndexOrThrow(Telephony.Carriers._ID)); for(ApnSetting p : mAllApnSettings) { ............ if (p.id == pos && p.canHandleType(mRequestedApnType)) { ............. cursor.close(); return p; } } } if (cursor != null) { cursor.close(); } ....... return null;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
从上面的代码可以看出perfer APN的选择,还是依赖于TelephonyProvider管理的数据库。
我们看看TelephonyProvider中查询Prefer APN的相关流程:
public synchronized Cursor query(Uri url, String[] projectionIn, String selection, String[] selectionArgs, String sort) { .......... int match = s_urlMatcher.match(url); switch (match) { ........... case URL_PREFERAPN: case URL_PREFERAPN_NO_UPDATE: { qb.appendWhere("_id = " + getPreferredApnId(subId, true)); break; } ........... } ......... SQLiteDatabase db = mOpenHelper.getReadableDatabase(); Cursor ret = null; try { ........... ret = qb.query(db, projectionIn, selection, selectionArgs, null, null, sort); } catch (SQLException e) { loge("got exception when querying: " + e); } ........ return ret;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
我们看看这里涉及到的getPreferredApnId函数:
private long getPreferredApnId(int subId, boolean checkApnSp) { SharedPreferences sp = getContext().getSharedPreferences(PREF_FILE_APN, Context.MODE_PRIVATE); long apnId = sp.getLong(COLUMN_APN_ID + subId, INVALID_APN_ID); if (apnId == INVALID_APN_ID && checkApnSp) { apnId = getPreferredApnIdFromApn(subId); if (apnId != INVALID_APN_ID) { setPreferredApnId(apnId, subId); deletePreferredApn(subId); } } return apnId;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
从上面的代码,我们知道TelephonyProvider为了加快prefer APN的检索速度,专门引入了SharedPreference单独保存每个卡对应的Prefer APN位置信息。
3.1.5 setDataProfilesAsNeeded
这一部分是将APN信息发往modem。
private void setDataProfilesAsNeeded() { ................ if (mAllApnSettings != null && !mAllApnSettings.isEmpty()) { ArrayList<DataProfile> dps = new ArrayList<DataProfile>(); for (ApnSetting apn : mAllApnSettings) { if (apn.modemCognitive) { DataProfile dp = new DataProfile(apn, mPhone.getServiceState().getDataRoaming()); boolean isDup = false; for(DataProfile dpIn : dps) { if (dp.equals(dpIn)) { isDup = true; break; } } } } if(dps.size() > 0) { mPhone.mCi.setDataProfile(dps.toArray(new DataProfile[0]), null); } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
modemCognitive参数默认是没有配置的,实际的APN也很少配置这个选项。
因此,上面的过程一般情况下并没有进行。
在厂商实际的代码流程中,框架在具体的场景中使用到APN时,才会向modem发送对应的DataProfile。
例如,我看过Qualcomm底层的实现代码,逻辑大致是:
在数据拨号时,拨号参数中携带了APN的参数。
在qcril_data_netctrl中通过QMI查询modem是否有对应的DataProfile,如果没有的话,就在QCRIL层构造dataProfile发送给modem。
modem将根据dataProfile的内容,来进行实际的网络接入。
3.2、setInitialAttachApn
createAllApnList结束后,我们已经得到了当前卡可以使用的APN,
如果用户之前选择过APN的话,我们还得到了Prefer APN。
有了这些信息后,我们就可以选择初始时使用的APN了。
终端初始时,数据卡应该是利用这个Initial Attach Apn注册到数据网络的。
我们看看setInitialAttachApn函数:
private void setInitialAttachApn() { ApnSetting iaApnSetting = null; ApnSetting defaultApnSetting = null; ApnSetting firstApnSetting = null; if (mAllApnSettings != null && !mAllApnSettings.isEmpty()) { firstApnSetting = mAllApnSettings.get(0); for (ApnSetting apn : mAllApnSettings) { if (ArrayUtils.contains(apn.types, PhoneConstants.APN_TYPE_IA) && apn.carrierEnabled) { iaApnSetting = apn; break; } else if ((defaultApnSetting == null) && (apn.canHandleType(PhoneConstants.APN_TYPE_DEFAULT))){ defaultApnSetting = apn; } } } ApnSetting initialAttachApnSetting = null; if (iaApnSetting != null) { initialAttachApnSetting = iaApnSetting; } else if (mPreferredApn != null) { initialAttachApnSetting = mPreferredApn; } else if (defaultApnSetting != null) { initialAttachApnSetting = defaultApnSetting; } else if (firstApnSetting != null) { initialAttachApnSetting = firstApnSetting; } if (initialAttachApnSetting == null) { .......... } else { ......... mPhone.mCi.setInitialAttachApn(initialAttachApnSetting.apn, initialAttachApnSetting.protocol, initialAttachApnSetting.authType, initialAttachApnSetting.user, initialAttachApnSetting.password, null); }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
上面的逻辑比较简单。
这里需要注意的是:当插着两张卡时,每个Phone应该都会下发对应的InitialAttachApn。
当终端在framework选择完可用的数据卡后,对应的Phone下发RIL_REQUEST_ALLOW_DATA,
于是modem就用数据Phone的InitialAttachApn注册数据网络。
至此,插卡后APN相关的主要流程介绍完毕,整个逻辑还是比较简单的,类似于下图:
现在我们看看在UI界面修改APN时,相关的流程。
二、UI界面修改APN的流程
在这一部分我们主要看看3个主要的操作:
1、对于同一张卡,进行切换APN的操作;
2、新建一个APN的操作;
3、重置APN的操作。
1、切换APN的操作
在原生代码中,ApnSettings界面负责显示一个卡可以使用的所有APN,该文件定义于 packages/apps/settings/src/com/android/settings中。
这里我们不深究界面显示相关问题,仅看看APN相关的主要内容:
public void onResume() { super.onResume(); ........... if (!mRestoreDefaultApnMode) { fillList(); }}
在ApnSettings界面的onResume函数中,利用fillList加载当前卡对应的APN Preference。
我们跟进一下fillList函数:
private void fillList() { final TelephonyManager tm = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE); final String mccmnc = mSubscriptionInfo == null ? "" : tm.getSimOperator(mSubscriptionInfo.getSubscriptionId()); 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')"); } 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) { r = mUiccController.getIccRecords(SubscriptionManager.getPhoneId( mSubscriptionInfo.getSubscriptionId()), UiccController.APP_FAM_3GPP); } PreferenceGroup apnList = (PreferenceGroup) findPreference("apn_list"); apnList.removeAll(); ArrayList<ApnPreference> mnoApnList = new ArrayList<ApnPreference>(); ArrayList<ApnPreference> mvnoApnList = new ArrayList<ApnPreference>(); ArrayList<ApnPreference> mnoMmsApnList = new ArrayList<ApnPreference>(); ArrayList<ApnPreference> mvnoMmsApnList = new ArrayList<ApnPreference>(); 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); ApnPreference pref = new ApnPreference(getPrefContext()); 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(); } 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; } for (Preference preference : mnoApnList) { apnList.addPreference(preference); } for (Preference preference : mnoMmsApnList) { apnList.addPreference(preference); } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
这一部分的内容比较简单,就是利用数据库得到当前卡对应的APN信息,然后构造对应的Preference显示到界面上。
ApnSettings加载完当前卡可用APN对应的Preference后,用户就可以手动进行点击和切换操作了。
当用户进行点击操作时,将进入到ApnEditor的界面,加载更加详细的APN信息:
public boolean onPreferenceTreeClick(Preference preference) { int pos = Integer.parseInt(preference.getKey()); Uri url = ContentUris.withAppendedId(Telephony.Carriers.CONTENT_URI, pos); startActivity(new Intent(Intent.ACTION_EDIT, url)); return true;}
我们先不深入ApnEditor界面,后面介绍新建APN的流程时,会再遇到这个类。
当用户进行切换操作后,ApnSettings的onPreferenceChange函数将被调用:
public boolean onPreferenceChange(Preference preference, Object newValue) { .......... if (newValue instanceof String) { setSelectedApnKey((String) newValue); } return true;}private void setSelectedApnKey(String key) { mSelectedKey = key; ContentResolver resolver = getContentResolver(); ContentValues values = new ContentValues(); values.put(APN_ID, mSelectedKey); resolver.update(PREFERAPN_URI, values, null, null);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
前面已经提到过,在DcTracker的构造函数中监听了数据库变化,代码如下:
........mApnObserver = new ApnChangeObserver();phone.getContext().getContentResolver().registerContentObserver( Telephony.Carriers.CONTENT_URI, true, mApnObserver);........
当APN的数据库发生变化时,ApnChangeObserver的onChange函数将被调用,发送DctConstants.EVENT_APN_CHANGED触发onApnChanged函数:
private void onApnChanged() { .......... createAllApnList(); setInitialAttachApn(); cleanUpConnectionsOnUpdatedApns(!isDisconnected); if (mPhone.getSubId() == SubscriptionManager.getDefaultDataSubscriptionId()) { setupDataOnConnectableApns(Phone.REASON_APN_CHANGED); }}
容易看出,当用户切换APN后,DcTracker将重新调用createAllApnList和setInitialAttachApn。
此时,DcTracker将用户选择的APN指定为prefer APN。
如果是数据卡的话,还会用新的perfer APN进行拨号。
1.1、cleanUpConnectionsOnUpdatedApns
这里我们跟进一下cleanUpConnectionsOnUpdatedApns函数,看看修改prefer APN后,在什么情况下会触发断开连接的操作。
private void cleanUpConnectionsOnUpdatedApns(boolean tearDown) { ............... if (mAllApnSettings.isEmpty()) { cleanUpAllConnections(tearDown, Phone.REASON_APN_CHANGED); } else { for (ApnContext apnContext : mApnContexts.values()) { ............. boolean cleanUpApn = true; ArrayList<ApnSetting> currentWaitingApns = apnContext.getWaitingApns(); if ((currentWaitingApns != null) && (!apnContext.isDisconnected())) { int radioTech = mPhone.getServiceState().getRilDataRadioTechnology(); ArrayList<ApnSetting> waitingApns = buildWaitingApns( apnContext.getApnType(), radioTech); ............. if (waitingApns.size() == currentWaitingApns.size()) { cleanUpApn = false; for (int i = 0; i < waitingApns.size(); i++) { if (!currentWaitingApns.get(i).equals(waitingApns.get(i))) { cleanUpApn = true; apnContext.setWaitingApns(waitingApns); break; } } } } if (cleanUpApn) { apnContext.setReason(Phone.REASON_APN_CHANGED); cleanUpConnection(true, apnContext); } } } ................}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
不同的ApnContext对应着不同的网络类型。
当修改APN数据库,使得一个ApnContext可用于拨号waitingApns发生变化时,就会断开当前ApnContext的连接,之后再进行重拨。
1.2 buildWaitingApns
现在我们来看看构造waitingApns相关的buildWaitingApns函数:
private ArrayList<ApnSetting> buildWaitingApns(String requestedApnType, int radioTech) { ................ ArrayList<ApnSetting> apnList = new ArrayList<ApnSetting>(); if (requestedApnType.equals(PhoneConstants.APN_TYPE_DUN)) { ApnSetting dun = fetchDunApn(); if (dun != null) { apnList.add(dun); return apnList; } } IccRecords r = mIccRecords.get(); String operator = (r != null) ? r.getOperatorNumeric() : ""; boolean usePreferred = true; try { usePreferred = ! mPhone.getContext().getResources().getBoolean(com.android. internal.R.bool.config_dontPreferApn); } catch (Resources.NotFoundException e) { ............ usePreferred = true; } if (usePreferred) { mPreferredApn = getPreferredApn(); } .............. if (usePreferred && mCanSetPreferApn && mPreferredApn != null && mPreferredApn.canHandleType(requestedApnType)) { if (mPreferredApn.numeric.equals(operator)) { if (ServiceState.bitmaskHasTech(mPreferredApn.bearerBitmask, radioTech)) { apnList.add(mPreferredApn); ........ return apnList; } else { setPreferredApn(-1); mPreferredApn = null; } } else { setPreferredApn(-1); mPreferredApn = null; } } ............ if (mAllApnSettings != null) { ............ for (ApnSetting apn : mAllApnSettings) { if (apn.canHandleType(requestedApnType)) { if (ServiceState.bitmaskHasTech(apn.bearerBitmask, radioTech)) { ......... apnList.add(apn); } else { ........ } } } } else { ......... } return apnList;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
buildWaitingApns的代码看起来比较复杂,其实上就是有可用的prefer APN时,选择prefer APN;
没有可用的prefer APN时,从现有卡对应的APN中,取出支持当前网络类型和无线技术的APN。
结合buildWaitingApns和cleanUpConnectionsOnUpdatedApns函数,我们可以知道:
1、对于数据卡而言,在连网状态下,当手动切换prefer APN时,如果这个prefer APN支持default类型,那么必然会断开原有连接,建立新的连接 (因为default ApnContext的waitingApns发生了变化,size或者内容发生改变)。
2、对于数据卡而言,在连网状态下,当我们新建或删除一个default类型的APN时,如果这个卡当前没有可用的prefer APN,那么也会断开原有连接,建立新的连接 (因为default ApnContext的waitingApns的size发生了变化)。
2、新建APN的操作
现在我们回过头来看看,APN界面相关的第二部分,即新建APN的操作。
原生代码中,新建APN的按键定义于ApnSettings界面的menu中,我们看看对应的代码:
public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MENU_NEW: addNewApn(); return true; ....... } return super.onOptionsItemSelected(item);}
跟进一下addNewApn函数:
private void addNewApn() { Intent intent = new Intent(Intent.ACTION_INSERT, Telephony.Carriers.CONTENT_URI); int subId = mSubscriptionInfo != null ? mSubscriptionInfo.getSubscriptionId() : SubscriptionManager.INVALID_SUBSCRIPTION_ID; intent.putExtra(SUB_ID, subId); if (!TextUtils.isEmpty(mMvnoType) && !TextUtils.isEmpty(mMvnoMatchData)) { intent.putExtra(MVNO_TYPE, mMvnoType); intent.putExtra(MVNO_MATCH_DATA, mMvnoMatchData); } startActivity(intent);}
随着流程,我们进入到了ApnEditor。先来看看ApnEditor的onCreate函数:
public 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"); ................ mRes = getResources(); final Intent intent = getIntent(); final String action = intent.getAction(); mSubId = intent.getIntExtra(ApnSettings.SUB_ID, SubscriptionManager.INVALID_SUBSCRIPTION_ID); mFirstTime = icicle == null; if (action.equals(Intent.ACTION_EDIT)) { Uri uri = intent.getData(); ....... mUri = uri; } else if (action.equals(Intent.ACTION_INSERT)){ if (mFirstTime || icicle.getInt(SAVED_POS) == 0) { Uri uri = intent.getData(); .......... mUri = getContentResolver().insert(uri, new ContentValues()); } else { .............. } mNewApn = true; ...................... } else { finish(); return; } mCursor = getActivity().managedQuery(mUri, sProjection, null, null); mCursor.moveToFirst(); ......... fillUi();}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
当ApnEditor界面被拉起来后,如果是查看已有的APN信息,那么fillUi函数会利用数据库中的信息填充界面;
如果是新建APN,此时UI界面的各字段就是空白的,等待用户进行填充。
需要注意的是,apns-conf.xml中加载的APN,默认是仅可读的,用户只能查看该类型的APN。
对于自己建立的APN,则可以在界面上进行编辑操作。
在ApnEditor的界面上,通过menu来保存或者删除APN,对应的代码如下:
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); if (!mNewApn) { menu.add(0, MENU_DELETE, 0, R.string.menu_delete) .setIcon(R.drawable.ic_menu_delete); } menu.add(0, MENU_SAVE, 0, R.string.menu_save) .setIcon(android.R.drawable.ic_menu_save); menu.add(0, MENU_CANCEL, 0, R.string.menu_cancel) .setIcon(android.R.drawable.ic_menu_close_clear_cancel);}
我们看看按键对应的处理代码:
public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case MENU_DELETE: deleteApn(); return true; case MENU_SAVE: if (validateAndSave(false)) { finish(); } return true; case MENU_CANCEL: if (mNewApn) { getContentResolver().delete(mUri, null, null); } finish(); return true; } return super.onOptionsItemSelected(item);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
我们主要关注保存新建APN使用的validateAndSave函数:
private boolean validateAndSave(boolean force) { String name = checkNotSet(mName.getText()); String apn = checkNotSet(mApn.getText()); String mcc = checkNotSet(mMcc.getText()); String mnc = checkNotSet(mMnc.getText()); if (getErrorMsg() != null && !force) { ErrorDialog.showError(this); return false; } if (force && mNewApn && name.length() < 1 && apn.length() < 1) { getContentResolver().delete(mUri, null, null); return false; } ContentValues values = new ContentValues(); values.put(Telephony.Carriers.NAME, name.length() < 1 ? getResources().getString(R.string.untitled_apn) : name); ................ getContentResolver().update(mUri, values, null, null); return true;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
至此,新建APN的流程基本介绍完毕,界面相关的工作其实还是比较简单的。
从上面的代码可以看出,最终新建的APN信息还是会被保存到数据库,因此也会触发DcTracker的流程。
3、重置APN的操作
在描述APN UI界面这一部分的最后,我们看看重置APN相关的操作。
重置APN就是将手机的APN恢复到出厂设置的状态,即移除所有用户添加的APN和当前卡的prefer APN相关的信息。
在ApnSettings界面的menu中提供了重置APN的按键,我们看看相关的处理函数:
public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { ............ case MENU_RESTORE: restoreDefaultApn(); return true; } ...........}private boolean restoreDefaultApn() { showDialog(DIALOG_RESTORE_DEFAULTAPN); mRestoreDefaultApnMode = true; if (mRestoreApnUiHandler == null) { mRestoreApnUiHandler = new RestoreApnUiHandler(); } if (mRestoreApnProcessHandler == null || mRestoreDefaultApnThread == null) { mRestoreDefaultApnThread = new HandlerThread( "Restore default APN Handler: Process Thread"); mRestoreDefaultApnThread.start(); mRestoreApnProcessHandler = new RestoreApnProcessHandler( mRestoreDefaultApnThread.getLooper(), mRestoreApnUiHandler); } mRestoreApnProcessHandler .sendEmptyMessage(EVENT_RESTORE_DEFAULTAPN_START); return true;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
我们看看RestoreApnProcessHandler中的处理EVENT_RESTORE_DEFAULTAPN_START的函数:
public void handleMessage(Message msg) { switch (msg.what) { case EVENT_RESTORE_DEFAULTAPN_START: ContentResolver resolver = getContentResolver(); resolver.delete(DEFAULTAPN_URI, null, null); mRestoreApnUiHandler .sendEmptyMessage(EVENT_RESTORE_DEFAULTAPN_COMPLETE); break; }}
我们先看看TelephonyProvider收到删除数据库消息的处理来流程:
public synchronized int delete(Uri url, String where, String[] whereArgs) { ............ SQLiteDatabase db = mOpenHelper.getWritableDatabase(); int match = s_urlMatcher.match(url); switch (match) { .......... case URL_RESTOREAPN: { count = 1; restoreDefaultAPN(subId); break; } ...... } ..........}
跟着流程进入到TelephonyProvider的restoreDefaultAPN函数:
private void restoreDefaultAPN(int subId) { SQLiteDatabase db = mOpenHelper.getWritableDatabase(); try { db.delete(CARRIERS_TABLE, null, null); } catch (SQLException e) { loge("got exception when deleting to restore: " + e); } setPreferredApnId((long) INVALID_APN_ID, subId); mOpenHelper.initDatabase(db);}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
当数据库相关的工作执行完毕后,ApnSettings中的RestoreApnUiHandler开始处理EVENT_RESTORE_DEFAULTAPN_COMPLETE消息:
public void handleMessage(Message msg) { switch (msg.what) { case EVENT_RESTORE_DEFAULTAPN_COMPLETE: Activity activity = getActivity(); if (activity == null) { mRestoreDefaultApnMode = false; return; } fillList(); getPreferenceScreen().setEnabled(true); mRestoreDefaultApnMode = false; removeDialog(DIALOG_RESTORE_DEFAULTAPN); ......... }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
整体来看,APN界面相关的工作还是比较简单的,主要的工作量还是集中在操作数据库上。
三、Android中APN配置相关的漏洞
在这篇博客的最后,我们来看看目前原生代码中APN配置相关的漏洞。这个漏洞将使得在某些场景下,用户明明关闭了数据业务开关,但后台应用仍然可以利用数据业务上网,消耗用户的流量。
前面我们已经提到过,初始时及APN界面改变时,DcTracker将创建当前卡可以使用的APN。
从前面的分析,我们知道DcTracker创建一个具体ApnSetting的函数为makeApnSetting,其中解析APN type的函数为parseTypes:
private String[] parseTypes(String types) { String[] result; if (types == null || types.equals("")) { result = new String[1]; result[0] = PhoneConstants.APN_TYPE_ALL; } else { result = types.split(","); } return result;}
当一个APN的type没有定义时,framework认为这个APN的类型为APN_TYPE_ALL。
因此,当一个用户新建一个APN,但不设置type域,或者apns-conf.xml中某个APN的type域没有配置时,这个APN的type就是APN_TYPE_ALL。
现在,我们看看framework拨号的部分流程:
private boolean onTrySetupData(String reason) { ............ setupDataOnConnectableApns(reason); return true;}private void setupDataOnConnectableApns(String reason) { setupDataOnConnectableApns(reason, RetryFailures.ALWAYS);}private void setupDataOnConnectableApns(String reason, RetryFailures retryFailures) { ......... for (ApnContext apnContext : mPrioritySortedApnContexts) { ArrayList<ApnSetting> waitingApns = null; ............. if (apnContext.isConnectable()) { .......... trySetupData(apnContext, waitingApns); } }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
这里我们仅截取重点的部分,关于ApnContext激活及数据业务拨号的详细过程可以参考:
Android7.0 数据拨号前的准备工作
Android7.0 数据业务长连接拨号过程
我们跟进一下trySetupData函数:
private boolean trySetupData(ApnContext apnContext, ArrayList<ApnSetting> waitingApns) { ................ if (apnContext.isConnectable() && (isEmergencyApn || (isDataAllowed && isDataAllowedForApn(apnContext) && isDataEnabled(checkUserDataEnabled) && !isEmergency())) && !mColdSimDetected ) { ............... int radioTech = mPhone.getServiceState().getRilDataRadioTechnology(); .............. if (apnContext.getState() == DctConstants.State.IDLE) { if (waitingApns == null) { waitingApns = buildWaitingApns(apnContext.getApnType(), radioTech); } if (waitingApns.isEmpty()) { ............ } else { apnContext.setWaitingApns(waitingApns); ........... } boolean retValue = setupData(apnContext, radioTech); ............ } } else { ......... }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
按照注释中的假设,我们知道当用户的数据卡包含一个type域为空的APN时,这个空APN可能会被加入到拨号使用的apnContext中,对应的type变为APN_TYPE_ALL。
我们跟进一下setupData函数:
private boolean setupData(ApnContext apnContext, int radioTech) { ........... apnSetting = apnContext.getNextApnSetting(); ........... int profileId = apnSetting.profileId; if (profileId == 0) { profileId = getApnProfileID(apnContext.getApnType()); } if (apnContext.getApnType() != PhoneConstants.APN_TYPE_DUN || teardownForDun() == false) { dcac = checkForCompatibleConnectedApnContext(apnContext); if (dcac != null) { ApnSetting dcacApnSetting = dcac.getApnSettingSync(); if (dcacApnSetting != null) { apnSetting = dcacApnSetting; } } } if (dcac == null) { if (isOnlySingleDcAllowed(radioTech)) { .............. } dcac = findFreeDataConnection(); if (dcac == null) { dcac = createDataConnection(); } ........... } final int generation = apnContext.incAndGetConnectionGeneration(); apnContext.setDataConnectionAc(dcac); apnContext.setApnSetting(apnSetting); apnContext.setState(DctConstants.State.CONNECTING); Message msg = obtainMessage(); msg.what = DctConstants.EVENT_DATA_SETUP_COMPLETE; msg.obj = new Pair<ApnContext, Integer>(apnContext, generation); dcac.bringUp(apnContext, profileId, radioTech, msg, generation); ....................}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
现在我们使用拨号的APN的type为APN_TYPE_ALL,到目前为止没有任何问题。
我们跟进DataConnection的流程:
private class DcInactiveState extends State { .......... public boolean processMessage(Message msg) { .......... switch (msg.what) { ....... case EVENT_CONNECT: ............. ConnectionParams cp = (ConnectionParams) msg.obj; if (initConnection(cp)) { onConnect(mConnectionParams); transitionTo(mActivatingState); } else { ......... } break; } ......... }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
这里我们跟进以下initConnection:
private boolean initConnection(ConnectionParams cp) { ApnContext apnContext = cp.mApnContext; if (mApnSetting == null) { mApnSetting = apnContext.getApnSetting(); } ............. mApnContexts.put(apnContext, cp); .............}
现在假设我们数据拨号成功,modem成功返回结果,DataConnection进入到自己的Active状态:
private class DcActiveState extends State { public void enter() { ......... boolean createNetworkAgent = true; ......... if (createNetworkAgent) { mNetworkAgent = new DcNetworkAgent(getHandler().getLooper(), mPhone.getContext(), "DcNetworkAgent", mNetworkInfo, makeNetworkCapabilities(), mLinkProperties, 50, misc); } } ...........}
在这里我们看看makeNetworkCapabilities函数:
private NetworkCapabilities makeNetworkCapabilities() { NetworkCapabilities result = new NetworkCapabilities() result.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR) if (mApnSetting != null) { //ApnSetting可以有多个Type,因此这里用来for循环 //这也是为什么很多厂商在配置APN时,会把一个支持default和mms的APN,拆分成两个 //主要是避免发送彩信时,建立起一个支持internet能力的连接,引起潜在的流量消耗 for (String type : mApnSetting.types) { switch (type) { case PhoneConstants.APN_TYPE_ALL: { //type_all具备了各种能力 result.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) result.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS) result.addCapability(NetworkCapabilities.NET_CAPABILITY_SUPL) result.addCapability(NetworkCapabilities.NET_CAPABILITY_FOTA) result.addCapability(NetworkCapabilities.NET_CAPABILITY_IMS) result.addCapability(NetworkCapabilities.NET_CAPABILITY_CBS) result.addCapability(NetworkCapabilities.NET_CAPABILITY_IA) break } case PhoneConstants.APN_TYPE_DEFAULT: { //default才有internet能力,终端可以用这个dataConnection访问网络 result.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) break } case PhoneConstants.APN_TYPE_MMS: { //mms的连接只能发彩信,不能上网 result.addCapability(NetworkCapabilities.NET_CAPABILITY_MMS) break } ............. } } ........ } .......}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
从上面的代码,我们可以看出,当用户利用type域为空的APN建立长连接时,该连接对应的网络支持各种能力。
在一般的情况下,这或许没有问题。但当用户使用的卡支持IMS功能,同时用户激活了IMS功能时,问题就来了。
IMS和MMS一样,都需要建立自己的DataConnection。
不同的是:MMS完成业务后,会断开建立的DataConnection。
但IMS建立的DataConnection将长时间存在,只是对应的网络没有访问Internet的能力罢了。
现在假设APN_TYPE_ALL的DataConnection已经建立成功了,手机卡支持IMS功能,需要建立一条IMS连接。
IMS建立DataConnection的流程与前面基本一致,不同的地方在setupData中,我们重新看看这部分代码:
private boolean setupData(ApnContext apnContext, int radioTech) { ........ if (apnContext.getApnType() != PhoneConstants.APN_TYPE_DUN || teardownForDun() == false) { dcac = checkForCompatibleConnectedApnContext(apnContext); if (dcac != null) { ApnSetting dcacApnSetting = dcac.getApnSettingSync(); if (dcacApnSetting != null) { apnSetting = dcacApnSetting; } } } if (dcac == null) { ......... } ............. dcac.bringUp(apnContext, profileId, radioTech, msg, generation); .............}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
我们跟进一下checkForCompatibleConnectedApnContext函数:
private DcAsyncChannel checkForCompatibleConnectedApnContext(ApnContext apnContext) { String apnType = apnContext.getApnType(); ............. DcAsyncChannel potentialDcac = null; ApnContext potentialApnCtx = null; for (ApnContext curApnCtx : mApnContexts.values()) { DcAsyncChannel curDcac = curApnCtx.getDcAc(); if (curDcac != null) { ApnSetting apnSetting = curApnCtx.getApnSetting(); if (dunSetting != null) { .......... } else if (apnSetting != null && apnSetting.canHandleType(apnType)) { switch (curApnCtx.getState()) { case CONNECTED: ......... return curDcac; case RETRYING: case CONNECTING: potentialDcac = curDcac; potentialApnCtx = curApnCtx; default: break; } } } else { ...... } } if (potentialDcac != null) { ........ return potentialDcac; } .........}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
从上面的代码不难看出,如果APN_TYPE_ALL对应的apn先于IMS APN被用于建立DataConnection,那么IMS APN将复用APN_TYPE_ALL对应的DataConnection。
我们再来回顾一下DataConnection的代码,此时DataConnection已经在DcActiveState:
private class DcActiveState extends State { .......... public boolean processMessage(Message msg) { ........ switch (msg.what) { case EVENT_CONNECT: { ConnectionParams cp = (ConnectionParams) msg.obj; mApnContexts.put(cp.mApnContext, cp); notifyConnectCompleted(cp, DcFailCause.NONE, false); retVal = HANDLED; break; } .......... } ...... }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
至此,IMS完全复用了APN_TYPE_ALL的dataConnection,因此IMS服务也将通过长连接对应的网络进行。
前面已经分析过,该网络是支持IMS服务的,因此没什么问题。
现在假设用户不愿意使用数据业务了,于是用户关闭了数据业务开关,这部分详细的流程可以参考Android7.0 数据业务长连接去拨号过程,此处我们仅关心APN相关的部分:
private void cleanUpConnection(boolean tearDown, ApnContext apnContext) { ........... DcAsyncChannel dcac = apnContext.getDcAc(); ........... if (tearDown) { if (apnContext.isDisconnected()) { ......... } else { if (dcac != null) { if (apnContext.getState() != DctConstants.State.DISCONNECTING) { boolean disconnectAll = false; ........ Message msg = obtainMessage(DctConstants.EVENT_DISCONNECT_DONE, pair); if (disconnectAll) { .......... } else { apnContext.getDcAc() .tearDown(apnContext, apnContext.getReason(), msg); } apnContext.setState(DctConstants.State.DISCONNECTING); mDisconnectPendingCount++; } } else { ......... } } } else { ........ } .........}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
看看DataConnection的DcActiveState处理流程:
private class DcActiveState extends State { .......... public boolean processMessage(Message msg) { ........ switch (msg.what) { ........ case EVENT_DISCONNECT: { DisconnectParams dp = (DisconnectParams) msg.obj; ........... if (mApnContexts.containsKey(dp.mApnContext)) { .......... if (mApnContexts.size() == 1) { mApnContexts.clear(); mDisconnectParams = dp; mConnectionParams = null; dp.mTag = mTag; tearDownData(dp); transitionTo(mDisconnectingState); } else { mApnContexts.remove(dp.mApnContext); notifyDisconnectCompleted(dp, false); } else { ............... } ............. } } ........ }}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
从上面的代码可以看出,当IMS复用APN_TYPE_ALL的dataConnection时,即使用户关闭数据业务开关,dataConnection也不会断开,仍然保留在DcActiveState。
public void exit() { ........ mNetworkInfo.setDetailedState(NetworkInfo.DetailedState.DISCONNECTED, reason, mNetworkInfo.getExtraInfo()); if (mNetworkAgent != null) { mNetworkAgent.sendNetworkInfo(mNetworkInfo); mNetworkAgent = null; }}
上面是DcActiveState的exit函数,从中可以看出只有DataConnection离开DcActiveState时,对应的NetworkAgent才会变成DISCONNECTED状态。
当dataConnection的NetworkAgent断开时,ConnectivityService才会清除对应的网络信息,例如路由之类的。
这一部分的内容比较长,我们再来回顾总结一下:
假设用户使用的是支持IMS网络的卡,同时激活了IMS能力。
用户选用了一个APN type域为空的APN作为prefer APN。
假设数据开关开机时是开启状态。
现在手机重启了,由于数据能力设置完成后,就会进行拨号操作,
因此长连接可能优先于IMS连接被建立成功。
在这种场景下,由于长连接对应的APN type被解析为APN_TYPE_ALL,因此IMS连接可能复用长连接。
当用户关闭数据开关后,由于IMS仍然需要使用该长连接,因此该长连接不会被断开。
ConnectivityService就不会通知应用网络断开,也不会清除网络对应的路由等信息。
因此,最终表现的结果就是数据开关明明是关闭状态,但后台还是在使用数据流量。
需要注意的是:
在分析时,我假设了许多条件,给人感觉好像这是个凭空想象出的问题。
实际上,这确实是国外用户上报的真实问题。
个人觉得,可能国外IMS普及率高,同时流量便宜(或者他们太土豪了),使得数据开关一直处于开启状态,导致这个问题会概率性发生。
基本上,只要APN的type域为空,同时数据连接先于IMS连接被建立起来,问题就会发生。
尽管我是用原生代码来分析,但Qualcomm和MTK都没有修复这个问题,主要依赖于具体的厂商来修复了。
总结
本篇博客主要分析了APN相关的常用流程,以及实际存在的APN相关的漏洞。
APN加载和编辑的内容比较单一,容易理解。
但APN相关漏洞这一部分,与数据业务拨号流程关联性比较大,需要先对数据业务有一定理解才能掌握。
最后说一下这个漏洞如何解决:
其实这个漏洞简单讲就是:APN的type为空时,建立的数据连接具有Internet和IMS能力;
IMS APN建立的连接将复用该数据连接后,导致该数据连接不受数据开关控制了。
解决方案大致分为三种:
1、parse Apn type时,对于用户建立的APN,如果type域为空时,不将其解析成APN_TYPE_ALL,而是解析成default。
只有apns-conf.xml中的默认APN的type为空时,才解析成APN_TYPE_ALL。
这样可以避免用户操作带来的问题,但apns-conf.xml配置有问题的话,该漏洞依然存在。
2、保证IMS连接优先于建立。
IMS可以复用APN_TYPE_ALL的连接;但APN_TYPE_ALL无法复用IMS的连接。
因此,若能保证IMS网络先完成注册,再进行数据拨号,那么该漏洞就不存在了。
但IMS网络的注册难以有效保证,这个思路可行,实际操作几乎是不可能的。
3、在判断是否复用DataConnection时,对IMS特殊处理一下。
正常情况下,使用default type的APN建立dataConnection,IMS本来就是无法复用的。
因此只需要修改DcTracker的setupData函数,判断当前的拨号的APN类型为IMS时,不复用任何连接即可。
这种方案是目前最简单有效的。
由于一些保密的原因,具体修改的代码此处就不附上了,按照上面的思路还是很好实现的。
原文地址:http://blog.csdn.net/gaugamela/article/details/53199141