解决Android模拟器中修改IMSI后无法上网问题

0x00 前言

百度百科中对IMSI的介绍如下:

国际移动用户识别码(IMSI:International Mobile Subscriber Identification Number)是区别移动用户的标志,储存在SIM卡中,可用于区别移动用户的有效信息。其总长度不超过15位,同样使用0~9的数字。其中MCC是移动用户所属国家代号,占3位数字,中国的MCC规定为460;MNC是移动网号码,由两位或者三位数字组成,中国移动的移动网络编码(MNC)为00;用于识别移动用户所归属的移动通信网;MSIN是移动用户识别码,用以识别某一移动通信网中的移动用户。

通过IMSI可以知道移动用户所在的国家。在Android中可以通过以下方法获取设备的IMSI号:

  1. TelephonyManager telephonyManager= (TelephonyManager)getSystemService(Context.TELEPHONY_SERVICE);
  2. String android_imsi = telephonyManager.getSubscriberId();
COPY

Android模拟器中默认使用的IMSI是:310260000000000。其中,310代表美国,260代表T-Mobile US。事实上,我们期望在模拟器上获取的IMSI应当以460开头(460代表中国)。

0x01 问题定位

但是,这串数字是硬编码在模拟器中的,路径是external/qemu/android/telephony/modem.c,只能通过修改模拟器源码来实现。

  1. #define OPERATOR_HOME_MCC 310
  2. #define OPERATOR_HOME_MNC 260
  3. #define OPERATOR_HOME_MCCMNC STRINGIFY(OPERATOR_HOME_MCC) \
  4. STRINGIFY(OPERATOR_HOME_MNC)
  5. { "+CIMI", OPERATOR_HOME_MCCMNC "0000000000", NULL }, /* request internation subscriber identification number */
COPY

将以上代码改为:

  1. #define OPERATOR_HOME_MCC 460 //中国
  2. #define OPERATOR_HOME_MNC 00 //移动
  3. #define OPERATOR_HOME_MCCMNC STRINGIFY(OPERATOR_HOME_MCC) \
  4. STRINGIFY(OPERATOR_HOME_MNC)
  5. { "+CIMI", OPERATOR_HOME_MCCMNC "00000000000", NULL }
COPY

重新编译,运行,使用getSubscriberId获取的值的确变成了我们期望的值:460000000000000
,但是出现了新的问题:模拟器不能上网了。一番Google之后,发现也有别人遇到了这个问题,但是也没有找到好的解决方法。

于是,决定自己寻找原因,从TelephonyManager一路翻下去,最终到ril层,也没有看出问题出在哪儿。但是,直觉告诉我,问题应当出在APN上。

Android系统中APN的配置信息是在/system/etc/apns-conf.xml中。下面是模拟器中默认的APN配置。

  1. <!-- use empty string to specify no proxy or port -->
  2. <!-- This version must agree with that in apps/common/res/apns.xml -->
  3. <apns version="8">
  4. <apn carrier="T-Mobile US"
  5. mcc="310"
  6. mnc="260"
  7. apn="epc.tmobile.com"
  8. user="none"
  9. server="*"
  10. password="none"
  11. mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
  12. />
  13. <apn carrier="T-Mobile US 250"
  14. mcc="310"
  15. mnc="250"
  16. apn="epc.tmobile.com"
  17. user="none"
  18. server="*"
  19. password="none"
  20. mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
  21. />
  22. <apn carrier="T-Mobile US 660"
  23. mcc="310"
  24. mnc="660"
  25. apn="epc.tmobile.com"
  26. user="none"
  27. server="*"
  28. password="none"
  29. mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
  30. />
  31. <apn carrier="T-Mobile US 230"
  32. mcc="310"
  33. mnc="230"
  34. apn="epc.tmobile.com"
  35. user="none"
  36. server="*"
  37. password="none"
  38. mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
  39. />
  40. <apn carrier="T-Mobile US 310"
  41. mcc="310"
  42. mnc="310"
  43. apn="epc.tmobile.com"
  44. user="none"
  45. server="*"
  46. password="none"
  47. mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
  48. />
  49. <apn carrier="T-Mobile US 580"
  50. mcc="310"
  51. mnc="580"
  52. apn="epc.tmobile.com"
  53. user="none"
  54. server="*"
  55. password="none"
  56. mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
  57. />
  58. <apn carrier="T-Mobile US 240"
  59. mcc="310"
  60. mnc="240"
  61. apn="epc.tmobile.com"
  62. user="none"
  63. server="*"
  64. password="none"
  65. mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
  66. />
  67. <apn carrier="T-Mobile US 800"
  68. mcc="310"
  69. mnc="800"
  70. apn="epc.tmobile.com"
  71. user="none"
  72. server="*"
  73. password="none"
  74. mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
  75. />
  76. <apn carrier="T-Mobile US 210"
  77. mcc="310"
  78. mnc="210"
  79. apn="epc.tmobile.com"
  80. user="none"
  81. server="*"
  82. password="none"
  83. mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
  84. />
  85. <apn carrier="T-Mobile US 160"
  86. mcc="310"
  87. mnc="160"
  88. apn="epc.tmobile.com"
  89. user="none"
  90. server="*"
  91. password="none"
  92. mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
  93. />
  94. <apn carrier="T-Mobile US 270"
  95. mcc="310"
  96. mnc="270"
  97. apn="epc.tmobile.com"
  98. user="none"
  99. server="*"
  100. password="none"
  101. mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
  102. />
  103. <apn carrier="T-Mobile US 200"
  104. mcc="310"
  105. mnc="200"
  106. apn="epc.tmobile.com"
  107. user="none"
  108. server="*"
  109. password="none"
  110. mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
  111. />
  112. <apn carrier="T-Mobile US 220"
  113. mcc="310"
  114. mnc="220"
  115. apn="epc.tmobile.com"
  116. user="none"
  117. server="*"
  118. password="none"
  119. mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
  120. />
  121. <apn carrier="T-Mobile US 490"
  122. mcc="310"
  123. mnc="490"
  124. apn="epc.tmobile.com"
  125. user="none"
  126. server="*"
  127. password="none"
  128. mmsc="http://mms.msg.eng.t-mobile.com/mms/wapenc"
  129. />
  130. <!-- T-Mobile Europe -->
  131. <apn carrier="T-Mobile UK"
  132. mcc="234"
  133. mnc="30"
  134. apn="general.t-mobile.uk"
  135. user="t-mobile"
  136. password="tm"
  137. server="*"
  138. mmsproxy="149.254.201.135"
  139. mmsport="8080"
  140. mmsc="http://mmsc.t-mobile.co.uk:8002"
  141. />
  142. <apn carrier="T-Mobile D"
  143. mcc="262"
  144. mnc="01"
  145. apn="internet.t-mobile"
  146. user="t-mobile"
  147. password="tm"
  148. server="*"
  149. mmsproxy="172.028.023.131"
  150. mmsport="8008"
  151. mmsc="http://mms.t-mobile.de/servlets/mms"
  152. />
  153. <apn carrier="T-Mobile A"
  154. mcc="232"
  155. mnc="03"
  156. apn="gprsinternet"
  157. user="t-mobile"
  158. password="tm"
  159. server="*"
  160. mmsproxy="010.012.000.020"
  161. mmsport="80"
  162. mmsc="http://mmsc.t-mobile.at/servlets/mms"
  163. type="default,supl"
  164. />
  165. <apn carrier="T-Mobile A MMS"
  166. mcc="232"
  167. mnc="03"
  168. apn="gprsmms"
  169. user="t-mobile"
  170. password="tm"
  171. server="*"
  172. mmsproxy="010.012.000.020"
  173. mmsport="80"
  174. mmsc="http://mmsc.t-mobile.at/servlets/mms"
  175. type="mms"
  176. />
  177. <apn carrier="T-Mobile CZ"
  178. mcc="230"
  179. mnc="01"
  180. apn="internet.t-mobile.cz"
  181. user="wap"
  182. password="wap"
  183. server="*"
  184. mmsproxy="010.000.000.010"
  185. mmsport="80"
  186. mmsc="http://mms"
  187. type="default,supl"
  188. />
  189. <apn carrier="T-Mobile CZ MMS"
  190. mcc="230"
  191. mnc="01"
  192. apn="mms.t-mobile.cz"
  193. user="mms"
  194. password="mms"
  195. server="*"
  196. mmsproxy="010.000.000.010"
  197. mmsport="80"
  198. mmsc="http://mms"
  199. type="mms"
  200. />
  201. <apn carrier="T-Mobile NL"
  202. mcc="204"
  203. mnc="16"
  204. apn="internet"
  205. user="*"
  206. password="*"
  207. server="*"
  208. mmsproxy="010.010.010.011"
  209. mmsport="8080"
  210. mmsc="http://t-mobilemms"
  211. type="default,supl"
  212. />
  213. <apn carrier="T-Mobile NL MMS"
  214. mcc="204"
  215. mnc="16"
  216. apn="mms"
  217. user="tmobilemms"
  218. password="tmobilemms"
  219. server="*"
  220. mmsproxy="010.010.010.011"
  221. mmsport="8080"
  222. mmsc="http://t-mobilemms"
  223. type="mms"
  224. />
  225. </apns>
COPY

可以看出,这里是没有国内运营商的配置的。我从小米手机中提取出的国内运营商配置。

  1. <apn carrier="中国移动 (China Mobile) GPRS"
  2. mcc="460"
  3. mnc="00"
  4. apn="cmnet"
  5. type="default,supl"
  6. />
  7. <apn carrier="中国移动 (China Mobile) WAP"
  8. mcc="460"
  9. mnc="00"
  10. apn="cmwap"
  11. port="80"
  12. proxy="10.0.0.172"
  13. type="default,supl"
  14. />
  15. <apn carrier="中国移动彩信 (China Mobile)"
  16. mcc="460"
  17. mnc="00"
  18. apn="cmwap"
  19. mmsc="http://mmsc.monternet.com"
  20. mmsport="80"
  21. mmsproxy="10.0.0.172"
  22. port="80"
  23. proxy="10.0.0.172"
  24. type="mms"
  25. />
  26. <apn carrier="沃3G连接互联网 (China Unicom)"
  27. mcc="460"
  28. mnc="01"
  29. apn="3gnet"
  30. type="default,supl"
  31. />
  32. <apn carrier="沃3G手机上网 (China Unicom)"
  33. mcc="460"
  34. mnc="01"
  35. apn="3gwap"
  36. port="80"
  37. proxy="10.0.0.172"
  38. type="default,supl"
  39. />
  40. <apn carrier="联通彩信 (China Unicom)"
  41. mcc="460"
  42. mnc="01"
  43. apn="3gwap"
  44. mmsc="http://mmsc.myuni.com.cn"
  45. mmsport="80"
  46. mmsproxy="10.0.0.172"
  47. type="mms"
  48. />
  49. <apn carrier="中国移动 (China Mobile) GPRS"
  50. mcc="460"
  51. mnc="02"
  52. apn="cmnet"
  53. type="default,supl"
  54. />
  55. <apn carrier="中国移动 (China Mobile) WAP"
  56. mcc="460"
  57. mnc="02"
  58. apn="cmwap"
  59. port="80"
  60. proxy="10.0.0.172"
  61. type="default,supl"
  62. />
  63. <apn carrier="中国移动彩信 (China Mobile)"
  64. mcc="460"
  65. mnc="02"
  66. apn="cmwap"
  67. mmsc="http://mmsc.monternet.com"
  68. mmsport="80"
  69. mmsproxy="10.0.0.172"
  70. port="80"
  71. proxy="10.0.0.172"
  72. type="mms"
  73. />
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

  1. /**
  2. * Based on the sim operator numeric, create a list for all possible
  3. * Data Connections and setup the preferredApn.
  4. */
  5. private void createAllApnList() {
  6. mAllApnSettings = new ArrayList<ApnSetting>();
  7. IccRecords r = mIccRecords.get();
  8. String operator = (r != null) ? r.getOperatorNumeric() : "";
  9. if (operator != null) {
  10. String selection = "numeric = '" + operator + "'";
  11. // query only enabled apn.
  12. // carrier_enabled : 1 means enabled apn, 0 disabled apn.
  13. // selection += " and carrier_enabled = 1";
  14. if (DBG) log("createAllApnList: selection=" + selection);
  15. Cursor cursor = mPhone.getContext().getContentResolver().query(
  16. Telephony.Carriers.CONTENT_URI, null, selection, null, null);
  17. if (cursor != null) {
  18. if (cursor.getCount() > 0) {
  19. mAllApnSettings = createApnList(cursor);
  20. }
  21. cursor.close();
  22. }
  23. }
  24. addEmergencyApnSetting();
  25. dedupeApnSettings();
  26. if (mAllApnSettings.isEmpty()) {
  27. if (DBG) log("createAllApnList: No APN found for carrier: " + operator);
  28. mPreferredApn = null;
  29. // TODO: What is the right behavior?
  30. //notifyNoData(DataConnection.FailCause.MISSING_UNKNOWN_APN);
  31. } else {
  32. mPreferredApn = getPreferredApn();
  33. if (mPreferredApn != null && !mPreferredApn.numeric.equals(operator)) {
  34. mPreferredApn = null;
  35. setPreferredApn(-1);
  36. }
  37. if (DBG) log("createAllApnList: mPreferredApn=" + mPreferredApn);
  38. }
  39. if (DBG) log("createAllApnList: X mAllApnSettings=" + mAllApnSettings);
  40. setDataProfilesAsNeeded();
  41. }
COPY

getOperatorNumeric的实现是在/frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/SIMRecords.java中。

  1. @Override
  2. public String getOperatorNumeric() {
  3. if (mImsi == null) {
  4. log("getOperatorNumeric: IMSI == null");
  5. return null;
  6. }
  7. if (mMncLength == UNINITIALIZED || mMncLength == UNKNOWN) {
  8. log("getSIMOperatorNumeric: bad mncLength");
  9. return null;
  10. }
  11. // Length = length of MCC + length of MNC
  12. // length of mcc = 3 (TS 23.003 Section 2.2)
  13. return mImsi.substring(0, 3 + mMncLength);
  14. }
COPY

可见,numeric的长度是受mMncLength控制的,而mMncLength的值是从SIM卡中读出来的。

  1. case EVENT_GET_AD_DONE:
  2. try {
  3. isRecordLoadResponse = true;
  4. ar = (AsyncResult)msg.obj;
  5. data = (byte[])ar.result;
  6. if (ar.exception != null) {
  7. break;
  8. }
  9. log("EF_AD: " + IccUtils.bytesToHexString(data));
  10. if (data.length < 3) {
  11. log("Corrupt AD data on SIM");
  12. break;
  13. }
  14. if (data.length == 3) {
  15. log("MNC length not present in EF_AD");
  16. break;
  17. }
  18. mMncLength = data[3] & 0xf;
  19. log("setting4 mMncLength=" + mMncLength);
  20. if (mMncLength == 0xf) {
  21. mMncLength = UNKNOWN;
  22. log("setting5 mMncLength=" + mMncLength);
  23. }
  24. }
COPY

最终定位到模拟器中的external/qemu/android/telephony/sim_card.c中。

  1. { "+CRSM=192,28589,0,0,15", "+CRSM: 144,0,000000046fad04000aa0aa01020000" },
  2. { "+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

  1. @Override
  2. public void onCreate(SQLiteDatabase db) {
  3. if (DBG) log("dbh.onCreate:+ db=" + db);
  4. createSimInfoTable(db);
  5. createCarriersTable(db);
  6. initDatabase(db);
  7. if (DBG) log("dbh.onCreate:- db=" + db);
  8. }
  9. private void initDatabase(SQLiteDatabase db) {
  10. if (VDBG) log("dbh.initDatabase:+ db=" + db);
  11. // Read internal APNS data
  12. Resources r = mContext.getResources();
  13. XmlResourceParser parser = r.getXml(com.android.internal.R.xml.apns);
  14. int publicversion = -1;
  15. try {
  16. XmlUtils.beginDocument(parser, "apns");
  17. publicversion = Integer.parseInt(parser.getAttributeValue(null, "version"));
  18. loadApns(db, parser);
  19. } catch (Exception e) {
  20. loge("Got exception while loading APN database." + e);
  21. } finally {
  22. parser.close();
  23. }
  24. // Read external APNS data (partner-provided)
  25. XmlPullParser confparser = null;
  26. // Environment.getRootDirectory() is a fancy way of saying ANDROID_ROOT or "/system".
  27. File confFile = new File(Environment.getRootDirectory(), PARTNER_APNS_PATH);
  28. File oemConfFile = new File(Environment.getOemDirectory(), OEM_APNS_PATH);
  29. if (oemConfFile.exists()) {
  30. // OEM image exist APN xml, get the timestamp from OEM & System image for comparison
  31. long oemApnTime = oemConfFile.lastModified();
  32. long sysApnTime = confFile.lastModified();
  33. if (DBG) log("APNs Timestamp: oemTime = " + oemApnTime + " sysTime = "
  34. + sysApnTime);
  35. // To get the latest version from OEM or System image
  36. if (oemApnTime > sysApnTime) {
  37. if (DBG) log("APNs Timestamp: OEM image is greater than System image");
  38. confFile = oemConfFile;
  39. }
  40. } else {
  41. // No Apn in OEM image, so load it from system image.
  42. if (DBG) log("No APNs in OEM image = " + oemConfFile.getPath() +
  43. " Load APNs from system image");
  44. }
  45. FileReader confreader = null;
  46. if (DBG) log("confFile = " + confFile);
  47. try {
  48. confreader = new FileReader(confFile);
  49. confparser = Xml.newPullParser();
  50. confparser.setInput(confreader);
  51. XmlUtils.beginDocument(confparser, "apns");
  52. // Sanity check. Force internal version and confidential versions to agree
  53. int confversion = Integer.parseInt(confparser.getAttributeValue(null, "version"));
  54. if (publicversion != confversion) {
  55. throw new IllegalStateException("Internal APNS file version doesn't match "
  56. + confFile.getAbsolutePath());
  57. }
  58. loadApns(db, confparser);
  59. } catch (FileNotFoundException e) {
  60. // It's ok if the file isn't found. It means there isn't a confidential file
  61. // Log.e(TAG, "File not found: '" + confFile.getAbsolutePath() + "'");
  62. } catch (Exception e) {
  63. loge("Exception while parsing '" + confFile.getAbsolutePath() + "'" + e);
  64. } finally {
  65. try { if (confreader != null) confreader.close(); } catch (IOException e) { }
  66. }
  67. if (VDBG) log("dbh.initDatabase:- db=" + db);
  68. }
  69. private DatabaseHelper mOpenHelper;
  70. private void restoreDefaultAPN(int subId) {
  71. SQLiteDatabase db = mOpenHelper.getWritableDatabase();
  72. try {
  73. db.delete(CARRIERS_TABLE, null, null);
  74. } catch (SQLException e) {
  75. loge("got exception when deleting to restore: " + e);
  76. }
  77. setPreferredApnId((long)-1, subId);
  78. mOpenHelper.initDatabase(db);
  79. }
COPY

可以看出,在restoreDefaultAPN的时候可以重新初始化CARRIERS_TABLE。也就说,只要进入APN界面,点击右上角菜单 => 重置为默认设置,就可以解决存量设备的上网问题了。

0x03 解决方法总结

  1. 修改模拟器源码modem.c中的MCC和MNC
  2. 修改模拟器源码sim_card.c中控制mMncLength的值
  3. 修改Android镜像中的/system/etc/apns-conf.xml,添加国内运营商的配置信息
  4. 存量设备更新APN
分享

Related Issues not found

Please contact @drunkdream to initialize the comment