0x00 前言
百度百科中对IMSI的介绍如下:
国际移动用户识别码(IMSI:International Mobile Subscriber Identification Number)是区别移动用户的标志,储存在SIM卡中,可用于区别移动用户的有效信息。其总长度不超过15位,同样使用0~9的数字。其中MCC是移动用户所属国家代号,占3位数字,中国的MCC规定为460;MNC是移动网号码,由两位或者三位数字组成,中国移动的移动网络编码(MNC)为00;用于识别移动用户所归属的移动通信网;MSIN是移动用户识别码,用以识别某一移动通信网中的移动用户。
通过IMSI可以知道移动用户所在的国家。在Android中可以通过以下方法获取设备的IMSI号:
TelephonyManager telephonyManager= (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);String android_imsi = telephonyManager.getSubscriberId();COPY
Android模拟器中默认使用的IMSI是:310260000000000。其中,310代表美国,260代表T-Mobile US。事实上,我们期望在模拟器上获取的IMSI应当以460开头(460代表中国)。
0x01 问题定位
但是,这串数字是硬编码在模拟器中的,路径是external/qemu/android/telephony/modem.c,只能通过修改模拟器源码来实现。
#define OPERATOR_HOME_MCC 310#define OPERATOR_HOME_MNC 260#define OPERATOR_HOME_MCCMNC STRINGIFY(OPERATOR_HOME_MCC) \STRINGIFY(OPERATOR_HOME_MNC){ "+CIMI", OPERATOR_HOME_MCCMNC "0000000000", NULL }, /* request internation subscriber identification number */COPY
将以上代码改为:
#define OPERATOR_HOME_MCC 460 //中国#define OPERATOR_HOME_MNC 00 //移动#define OPERATOR_HOME_MCCMNC STRINGIFY(OPERATOR_HOME_MCC) \STRINGIFY(OPERATOR_HOME_MNC){ "+CIMI", OPERATOR_HOME_MCCMNC "00000000000", NULL }COPY
重新编译,运行,使用getSubscriberId获取的值的确变成了我们期望的值:460000000000000
,但是出现了新的问题:模拟器不能上网了。一番Google之后,发现也有别人遇到了这个问题,但是也没有找到好的解决方法。
于是,决定自己寻找原因,从TelephonyManager一路翻下去,最终到ril层,也没有看出问题出在哪儿。但是,直觉告诉我,问题应当出在APN上。
Android系统中APN的配置信息是在/system/etc/apns-conf.xml中。下面是模拟器中默认的APN配置。
<!-- use empty string to specify no proxy or port --><!-- This version must agree with that in apps/common/res/apns.xml --><apns version="8"><apn carrier="T-Mobile US"mcc="310"mnc="260"apn="epc.tmobile.com"user="none"server="*"password="none"mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"/><apn carrier="T-Mobile US 250"mcc="310"mnc="250"apn="epc.tmobile.com"user="none"server="*"password="none"mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"/><apn carrier="T-Mobile US 660"mcc="310"mnc="660"apn="epc.tmobile.com"user="none"server="*"password="none"mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"/><apn carrier="T-Mobile US 230"mcc="310"mnc="230"apn="epc.tmobile.com"user="none"server="*"password="none"mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"/><apn carrier="T-Mobile US 310"mcc="310"mnc="310"apn="epc.tmobile.com"user="none"server="*"password="none"mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"/><apn carrier="T-Mobile US 580"mcc="310"mnc="580"apn="epc.tmobile.com"user="none"server="*"password="none"mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"/><apn carrier="T-Mobile US 240"mcc="310"mnc="240"apn="epc.tmobile.com"user="none"server="*"password="none"mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"/><apn carrier="T-Mobile US 800"mcc="310"mnc="800"apn="epc.tmobile.com"user="none"server="*"password="none"mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"/><apn carrier="T-Mobile US 210"mcc="310"mnc="210"apn="epc.tmobile.com"user="none"server="*"password="none"mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"/><apn carrier="T-Mobile US 160"mcc="310"mnc="160"apn="epc.tmobile.com"user="none"server="*"password="none"mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"/><apn carrier="T-Mobile US 270"mcc="310"mnc="270"apn="epc.tmobile.com"user="none"server="*"password="none"mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"/><apn carrier="T-Mobile US 200"mcc="310"mnc="200"apn="epc.tmobile.com"user="none"server="*"password="none"mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"/><apn carrier="T-Mobile US 220"mcc="310"mnc="220"apn="epc.tmobile.com"user="none"server="*"password="none"mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"/><apn carrier="T-Mobile US 490"mcc="310"mnc="490"apn="epc.tmobile.com"user="none"server="*"password="none"mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"/><!-- T-Mobile Europe --><apn carrier="T-Mobile UK"mcc="234"mnc="30"apn="general.t-mobile.uk"user="t-mobile"password="tm"server="*"mmsproxy="149.254.201.135"mmsport="8080"mmsc="http://mmsc.t-mobile.co.uk:8002"/><apn carrier="T-Mobile D"mcc="262"mnc="01"apn="internet.t-mobile"user="t-mobile"password="tm"server="*"mmsproxy="172.028.023.131"mmsport="8008"mmsc="http://mms.t-mobile.de/servlets/mms"/><apn carrier="T-Mobile A"mcc="232"mnc="03"apn="gprsinternet"user="t-mobile"password="tm"server="*"mmsproxy="010.012.000.020"mmsport="80"mmsc="http://mmsc.t-mobile.at/servlets/mms"type="default,supl"/><apn carrier="T-Mobile A MMS"mcc="232"mnc="03"apn="gprsmms"user="t-mobile"password="tm"server="*"mmsproxy="010.012.000.020"mmsport="80"mmsc="http://mmsc.t-mobile.at/servlets/mms"type="mms"/><apn carrier="T-Mobile CZ"mcc="230"mnc="01"apn="internet.t-mobile.cz"user="wap"password="wap"server="*"mmsproxy="010.000.000.010"mmsport="80"mmsc="http://mms"type="default,supl"/><apn carrier="T-Mobile CZ MMS"mcc="230"mnc="01"apn="mms.t-mobile.cz"user="mms"password="mms"server="*"mmsproxy="010.000.000.010"mmsport="80"mmsc="http://mms"type="mms"/><apn carrier="T-Mobile NL"mcc="204"mnc="16"apn="internet"user="*"password="*"server="*"mmsproxy="010.010.010.011"mmsport="8080"mmsc="http://t-mobilemms"type="default,supl"/><apn carrier="T-Mobile NL MMS"mcc="204"mnc="16"apn="mms"user="tmobilemms"password="tmobilemms"server="*"mmsproxy="010.010.010.011"mmsport="8080"mmsc="http://t-mobilemms"type="mms"/></apns>COPY
可以看出,这里是没有国内运营商的配置的。我从小米手机中提取出的国内运营商配置。
<apn carrier="中国移动 (China Mobile) GPRS"mcc="460"mnc="00"apn="cmnet"type="default,supl"/><apn carrier="中国移动 (China Mobile) WAP"mcc="460"mnc="00"apn="cmwap"port="80"proxy="10.0.0.172"type="default,supl"/><apn carrier="中国移动彩信 (China Mobile)"mcc="460"mnc="00"apn="cmwap"mmsc="http://mmsc.monternet.com"mmsport="80"mmsproxy="10.0.0.172"port="80"proxy="10.0.0.172"type="mms"/><apn carrier="沃3G连接互联网 (China Unicom)"mcc="460"mnc="01"apn="3gnet"type="default,supl"/><apn carrier="沃3G手机上网 (China Unicom)"mcc="460"mnc="01"apn="3gwap"port="80"proxy="10.0.0.172"type="default,supl"/><apn carrier="联通彩信 (China Unicom)"mcc="460"mnc="01"apn="3gwap"mmsc="http://mmsc.myuni.com.cn"mmsport="80"mmsproxy="10.0.0.172"type="mms"/><apn carrier="中国移动 (China Mobile) GPRS"mcc="460"mnc="02"apn="cmnet"type="default,supl"/><apn carrier="中国移动 (China Mobile) WAP"mcc="460"mnc="02"apn="cmwap"port="80"proxy="10.0.0.172"type="default,supl"/><apn carrier="中国移动彩信 (China Mobile)"mcc="460"mnc="02"apn="cmwap"mmsc="http://mmsc.monternet.com"mmsport="80"mmsproxy="10.0.0.172"port="80"proxy="10.0.0.172"type="mms"/>COPY
添加到apns-conf.xml中,重启模拟器,能识别出“中国移动”了,但依然不能上网。
此时,我想到通过对比默认情况和修改后的radio日志,来分析原因。
使用
adb logcat -b radio
命令可以查查看ril相关的日志。接着,就发现了如下两条日志:
D/DCT ( 1739): [0]createAllApnList: selection=numeric = ‘460000’
D/DCT ( 1739): [0]createAllApnList: No APN found for carrier: 460000
注意到这里的460000,正确的应该是46000,多了一个0。由于美国的MNC是3个字符,而中国的MNC是2个字符,所以导致这里多了一个字符。来看下源码在这里是怎么实现的。
/frameworks/opt/telephony/src/java/com/android/internal/telephony/dataconnection/DcTracker.java
/*** Based on the sim operator numeric, create a list for all possible* Data Connections and setup the preferredApn.*/private void createAllApnList() {mAllApnSettings = new ArrayList<ApnSetting>();IccRecords r = mIccRecords.get();String operator = (r != null) ? r.getOperatorNumeric() : "";if (operator != null) {String selection = "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);Cursor cursor = mPhone.getContext().getContentResolver().query(Telephony.Carriers.CONTENT_URI, null, selection, null, null);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();}COPY
getOperatorNumeric的实现是在/frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/SIMRecords.java中。
@Overridepublic String getOperatorNumeric() {if (mImsi == null) {log("getOperatorNumeric: IMSI == null");return null;}if (mMncLength == UNINITIALIZED || mMncLength == UNKNOWN) {log("getSIMOperatorNumeric: bad mncLength");return null;}// Length = length of MCC + length of MNC// length of mcc = 3 (TS 23.003 Section 2.2)return mImsi.substring(0, 3 + mMncLength);}COPY
可见,numeric的长度是受mMncLength控制的,而mMncLength的值是从SIM卡中读出来的。
case EVENT_GET_AD_DONE:try {isRecordLoadResponse = true;ar = (AsyncResult)msg.obj;data = (byte[])ar.result;if (ar.exception != null) {break;}log("EF_AD: " + IccUtils.bytesToHexString(data));if (data.length < 3) {log("Corrupt AD data on SIM");break;}if (data.length == 3) {log("MNC length not present in EF_AD");break;}mMncLength = data[3] & 0xf;log("setting4 mMncLength=" + mMncLength);if (mMncLength == 0xf) {mMncLength = UNKNOWN;log("setting5 mMncLength=" + mMncLength);}}COPY
最终定位到模拟器中的external/qemu/android/telephony/sim_card.c中。
{ "+CRSM=192,28589,0,0,15", "+CRSM: 144,0,000000046fad04000aa0aa01020000" },{ "+CRSM=176,28589,0,0,4", "+CRSM: 144,0,00000003" },COPY
将00000003改成00000002,重新编译,运行,终于可以正常上网了。
0x02 解决更新问题
此时,对于新创建的模拟器已经正常了,但是对于存量模拟器,由于telephony.db数据库中的carriers表中的数据没有更新,因此重启后还是不能上网。
查看carriers表中的内容可以在adb shell中执行命令:content query --uri content://telephony/carriers
/packages/providers/TelephonyProvider/src/com/android/providers/telephony/TelephonyProvider.java
@Overridepublic void onCreate(SQLiteDatabase db) {if (DBG) log("dbh.onCreate:+ db=" + db);createSimInfoTable(db);createCarriersTable(db);initDatabase(db);if (DBG) log("dbh.onCreate:- db=" + db);}private void initDatabase(SQLiteDatabase db) {if (VDBG) log("dbh.initDatabase:+ db=" + db);// Read internal APNS dataResources 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;// Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system".File confFile = new File(Environment.getRootDirectory(), PARTNER_APNS_PATH);File oemConfFile = new File(Environment.getOemDirectory(), OEM_APNS_PATH);if (oemConfFile.exists()) {// OEM image exist APN xml, get the timestamp from OEM & System image for comparisonlong oemApnTime = oemConfFile.lastModified();long sysApnTime = confFile.lastModified();if (DBG) log("APNs Timestamp: oemTime = " + oemApnTime + " sysTime = "+ sysApnTime);// To get the latest version from OEM or System imageif (oemApnTime > sysApnTime) {if (DBG) log("APNs Timestamp: OEM image is greater than System image");confFile = oemConfFile;}} else {// No Apn in OEM image, so load it from system image.if (DBG) log("No APNs in OEM image = " + oemConfFile.getPath() +" Load APNs from system image");}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 agreeint confversion = Integer.parseInt(confparser.getAttributeValue(null, "version"));if (publicversion != confversion) {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("Exception while parsing '" + confFile.getAbsolutePath() + "'" + e);} finally {try { if (confreader != null) confreader.close(); } catch (IOException e) { }}if (VDBG) log("dbh.initDatabase:- db=" + db);}private DatabaseHelper mOpenHelper;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)-1, subId);mOpenHelper.initDatabase(db);}COPY
可以看出,在restoreDefaultAPN的时候可以重新初始化CARRIERS_TABLE。也就说,只要进入APN界面,点击右上角菜单 => 重置为默认设置,就可以解决存量设备的上网问题了。
0x03 解决方法总结
- 修改模拟器源码modem.c中的MCC和MNC
- 修改模拟器源码sim_card.c中控制mMncLength的值
- 修改Android镜像中的/system/etc/apns-conf.xml,添加国内运营商的配置信息
- 存量设备更新APN
Gitalking ...