Uicc之IccRecords

        与IccFileHandler类似,UiccCardApplication也会根据当前SIM卡的类型创建不同的IccRecords对象,这个对象与IccFileHandler的区别在于,IccFileHandler是以SIM文件系统为操作对象,而IccRecords是以SIM存储内容为操作对象(IccFileHandler偏重底层实现,IccRecords偏重上层应用)
        下面是IccRecords不同的子类对象:
  1. @UiccCardApplication. java
  2. private IccRecords createIccRecords (AppType type, Context c, CommandsInterface ci) {
  3. if (type == AppType.APPTYPE_USIM || type == AppType.APPTYPE_SIM) {
  4. return new SIMRecords( this, c, ci);
  5. } else if (type == AppType.APPTYPE_RUIM || type == AppType.APPTYPE_CSIM){
  6. return new RuimRecords( this, c, ci);
  7. } else if (type == AppType.APPTYPE_ISIM) {
  8. return new IsimUiccRecords( this, c, ci);
  9. } else {
  10. return null;
  11. }
  12. }

        我们仍然挑选典型的SIMRecords来分析。


一、SIMRecords的主要作用


        我们来看其提供的主要public方法:
  1. public String getIMSI() {}
  2. public String getMsisdnNumber() {}
  3. public String getGid1() {}
  4. public UsimServiceTable getUsimServiceTable() {}
  5. public void setMsisdnNumber(String alphaTag, String number, Message onComplete) {}
  6. public String getMsisdnAlphaTag() {}
  7. public String getVoiceMailNumber() {}
  8. public void setVoiceMailNumber(String alphaTag, String voiceNumber, Message onComplete) {}
  9. public String getVoiceMailAlphaTag(){}
  10. public void setVoiceMessageWaiting(int line, int countWaiting) {}
  11. public boolean getVoiceCallForwardingFlag() {}
  12. public String getOperatorNumeric() {}
  13. public int getDisplayRule(String plmn) {}
  14. public boolean isCspPlmnEnabled() {}
        同时来看一下其集成的父类IccRecords提供的public方法:
  1. public AdnRecordCache getAdnCache() {}
  2. public String getIccId() {}
  3. public void setImsi(String imsi) {}
  4. public String getServiceProviderName() {}
  5. public boolean getVoiceMessageWaiting() {}
  6. public int getVoiceMessageCount() {}
  7. public boolean getRecordsLoaded() {}
  8. public void setVoiceCallForwardingFlag(int line, boolean enable, String number) {}
  9. public boolean isProvisioned () {}
  10. public IsimRecords getIsimRecords() {}
  11. public void registerForRecordsLoaded(Handler h, int what, Object obj) {}
  12. public void registerForImsiReady(Handler h, int what, Object obj) {}
  13. public void registerForRecordsEvents(Handler h, int what, Object obj) {}
  14. public void registerForNewSms(Handler h, int what, Object obj) {}
  15. public void registerForNetworkSelectionModeAutomatic( Handler h, int what, Object obj) {}
        从这些方法看出,IccRecords的主要功能分为两部分:
        1、提供SIM卡常用信息的查询,包括IMSI、VoiceMail、ICCID、SIMRecords等信息;

        2、注册常用信息的监听器,包括SIMRecords、IMSI、RecordEvents、NewSms、NetworkSelection等事件;


二、SIMRecords的创建过程


        从其继承关系可以看出,他的 本质也是Handler
  1. public class SIMRecords extends IccRecords {}
  2. public abstract class IccRecords extends Handler implements IccConstants {}
        然后看他的构造函数:
  1. public SIMRecords(UiccCardApplication app, Context c, CommandsInterface ci) {
  2. //在父类中对mCi、mFh等变量初始化
  3. super(app, c, ci);
  4. //创建Adn缓存,用于操作SIM卡联系人
  5. mAdnCache = new AdnRecordCache(mFh);
  6. //创建VoiceMail缓存
  7. mVmConfig = new VoiceMailConstants();
  8. mSpnOverride = new SpnOverride();
  9. mRecordsRequested = false; // No load request is made till SIM ready
  10. mRecordsToLoad = 0;
  11. mCi.setOnSmsOnSim( this, EVENT_SMS_ON_SIM, null);
  12. mCi.registerForIccRefresh( this, EVENT_SIM_REFRESH, null);
  13. //初始化成员变量
  14. resetRecords();
  15. //监听UiccCardApplication的Ready状态
  16. mParentApp.registerForReady( this, EVENT_APP_READY, null);
  17. }
        以上的创建过程完成了两个重要任务:
        1、创建Adn和VoiceMail缓存,这里的Adn缓存用于SIM卡联系人的增、删、改、查等功能;

        2、监听UiccCardApplication的Ready状态;


三、SIMRecords的更新过程


        当监听的UiccCardApplication发送Ready状态之后,SIMRecords就会更新自己所拥有的Adn和VoiceMail缓存:
  1. protected void fetchSimRecords() {
  2. mRecordsRequested = true;
  3. //获取SIM卡的IMSI
  4. mCi.getIMSIForApp(mParentApp.getAid(), obtainMessage(EVENT_GET_IMSI_DONE));
  5. mRecordsToLoad++;
  6. //得到SIM卡的ICCID
  7. mFh.loadEFTransparent(EF_ICCID, obtainMessage(EVENT_GET_ICCID_DONE));
  8. mRecordsToLoad++;
  9. new AdnRecordLoader(mFh).loadFromEF(EF_MSISDN, EF_EXT1, 1, obtainMessage(EVENT_GET_MSISDN_DONE));
  10. mRecordsToLoad++;
  11. //更新VoiceMail
  12. mFh.loadEFLinearFixed(EF_MBI, 1, obtainMessage(EVENT_GET_MBI_DONE));
  13. mRecordsToLoad++;
  14. mFh.loadEFTransparent(EF_AD, obtainMessage(EVENT_GET_AD_DONE));
  15. mRecordsToLoad++;
  16. // Record number is subscriber profile
  17. mFh.loadEFLinearFixed(EF_MWIS, 1, obtainMessage(EVENT_GET_MWIS_DONE));
  18. mRecordsToLoad++;
  19. mFh.loadEFTransparent( EF_VOICE_MAIL_INDICATOR_CPHS, obtainMessage(EVENT_GET_VOICE_MAIL_INDICATOR_CPHS_DONE));
  20. mRecordsToLoad++;
  21. mFh.loadEFLinearFixed(EF_CFIS, 1, obtainMessage(EVENT_GET_CFIS_DONE));
  22. mRecordsToLoad++;
  23. mFh.loadEFTransparent(EF_CFF_CPHS, obtainMessage(EVENT_GET_CFF_DONE));
  24. mRecordsToLoad++;
  25. getSpnFsm( true, null);
  26. mFh.loadEFTransparent(EF_SPDI, obtainMessage(EVENT_GET_SPDI_DONE));
  27. mRecordsToLoad++;
  28. mFh.loadEFLinearFixed(EF_PNN, 1, obtainMessage(EVENT_GET_PNN_DONE));
  29. mRecordsToLoad++;
  30. mFh.loadEFTransparent(EF_SST, obtainMessage(EVENT_GET_SST_DONE));
  31. mRecordsToLoad++;
  32. mFh.loadEFTransparent(EF_INFO_CPHS, obtainMessage(EVENT_GET_INFO_CPHS_DONE));
  33. mRecordsToLoad++;
  34. mFh.loadEFTransparent(EF_CSP_CPHS,obtainMessage(EVENT_GET_CSP_CPHS_DONE));
  35. mRecordsToLoad++;
  36. mFh.loadEFTransparent(EF_GID1, obtainMessage(EVENT_GET_GID1_DONE));
  37. mRecordsToLoad++;
  38. }

        从上面可以看出,SIMRecords的更新过程就是用IccFileHandler将常用的SIM卡信息,读取并保存,其中就包括IMSI和ICCID等信息。


四、VoiceMail的设置与读取


        由于VoiceMail的号码和名称比较常用,因此我们专门来看以下VoiceMail的读取和更新机制。
        首先普及一下语音信箱的使用方法: 语音信箱需要和呼叫转移相互搭配才能使用,通过呼叫转移来设置触发语音信箱的条件。比如可以设置“无应答转移”到语音信箱,这样一来,有来电时,如果长时间无应答,运营商就会将该来点转移到预先设置的语音信箱中,然后录取通话音频,并在用户拨打语音信箱时播放给用户。
        而不同的运营商的语音信箱号码是不固定的,有的运营商的语音信箱号码是固定的,有的是可以让用户设置的,对于中国移动来说,某个地区的语音信箱是一样的。
        针对以上情况,Google设计中,VoiceMail的来源可以有两个地方,一个是从SIM卡中读取,也就是运营商在SIM卡中预置。另一个是从内置的配置文件中读取,也就是针对一些常用的运营商,如果他们的语音信箱是固定的,那么就可以直接预置在代码中,此时 由于已知该运营商的语音信箱号码固定,所以用户是无法修改语音信箱号码的

        对于第二种途径可以进行更丰富的客制化操作。下面我们分别来看这两种途径。


4.1、从SIM卡中读取VoiceMail信息

        

        SIM卡中有两个地方可以存储VoiceMail信息(EF_MBDN、EF_MAILBOX_CPHS),我们可以通过读取EF_MBI分区来获取VoiceMail的存储位置。

        前面分析过,SIMRecords会在接收到UiccCardApplication的Ready通知后更新SIMRecords信息,其中就包括向Modem请求SIM中的VoiceMail的存储位置信息:
  1. @SIMRecords. java
  2. protected void fetchSimRecords () {
  3. //更新VoiceMail
  4. mFh.loadEFLinearFixed(EF_MBI, 1, obtainMessage(EVENT_GET_MBI_DONE));
  5. mRecordsToLoad++;
  6. }
        当接收到EF_MBI中的信息后,由handleMessage()负责解析:
  1. public void handleMessage(Message msg) {
  2. switch (msg.what) {
  3. case EVENT_GET_MBI_DONE:
  4. boolean isValidMbdn;
  5. isRecordLoadResponse = true;
  6. ar = (AsyncResult)msg.obj;
  7. data = ( byte[]) ar.result;
  8. isValidMbdn = false;
  9. if (ar.exception == null) {
  10. mMailboxIndex = data[ 0] & 0xff;
  11. if (mMailboxIndex != 0 && mMailboxIndex != 0xff) {
  12. //得到VoiceMail的存储位置
  13. isValidMbdn = true;
  14. }
  15. }
  16. mRecordsToLoad += 1;
  17. if (isValidMbdn) {
  18. // Note: MBDN was not included in NUM_OF_SIM_RECORDS_LOADED
  19. //读取EF_MBDN中的VoiceMail
  20. new AdnRecordLoader(mFh).loadFromEF(EF_MBDN, EF_EXT6, mMailboxIndex, obtainMessage(EVENT_GET_MBDN_DONE));
  21. } else {
  22. //读取EF_MAILBOX_CPHS中的VoiceMail
  23. new AdnRecordLoader(mFh).loadFromEF(EF_MAILBOX_CPHS, EF_EXT1, 1, obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE));
  24. }
  25. break;
  26. }
  27. }
        我们看到,经过对返回值的解析,得到了VoiceMail的存储位置,接着就向Modem申请该位置中的VoiceMail信息,当接收到Modem的反馈后,会再次进入handleMessage()中解析:
  1. public void handleMessage(Message msg) {
  2. switch (msg.what) {
  3. case EVENT_GET_CPHS_MAILBOX_DONE:
  4. case EVENT_GET_MBDN_DONE:
  5. mVoiceMailNum = null;
  6. mVoiceMailTag = null;
  7. isRecordLoadResponse = true;
  8. ar = (AsyncResult)msg.obj;
  9. adn = (AdnRecord)ar.result;
  10. if (adn.isEmpty() && msg.what == EVENT_GET_MBDN_DONE) {
  11. mRecordsToLoad += 1;
  12. //如果当前的EF_MBDN分区读取失败,再次尝试使用EF_MAILBOX_CPHS分区读取VoiceMail信息
  13. new AdnRecordLoader(mFh).loadFromEF( EF_MAILBOX_CPHS, EF_EXT1, 1, obtainMessage(EVENT_GET_CPHS_MAILBOX_DONE));
  14. break;
  15. }
  16. //得到VoiceMail的Number和Tag
  17. mVoiceMailNum = adn.getNumber();
  18. mVoiceMailTag = adn.getAlphaTag();
  19. break;
  20. }
  21. }

        由此,我们就从SIM卡中读取到了VoiceMail信息。


4.2、从配置文件中读取VoiceMail信息

        

        在SIMRecords的构造函数中初始化了一个特殊的对象:

  1. public SIMRecords(UiccCardApplication app, Context c, CommandsInterface ci) {
  2. super(app, c, ci);
  3. mVmConfig = new VoiceMailConstants();
  4. }
        这里出现的VoiceMailConstants就是专门为VoiceMail的配置文件而创建的,他的主要作用就是, 读取并解析系统配置文件,并根据当前的MCC/MNC获取相应的VoiceMail
        先来看以下这个类的构造方法流程:
  1. @VoiceMailConstants. java
  2. VoiceMailConstants () {
  3. CarrierVmMap = new HashMap<String, String[]>();
  4. loadVoiceMail();
  5. }
  6. private void loadVoiceMail() {
  7. FileReader vmReader;
  8. //获取配置文件,路径:"etc/voicemail-conf.xml"
  9. final File vmFile = new File(Environment.getRootDirectory(), PARTNER_VOICEMAIL_PATH);
  10. try {
  11. vmReader = new FileReader(vmFile);
  12. } catch (FileNotFoundException e) {
  13. }
  14. try {
  15. XmlPullParser parser = Xml.newPullParser();
  16. parser.setInput(vmReader);
  17. XmlUtils.beginDocument(parser, "voicemail");
  18. while ( true) {
  19. //解析XML文件
  20. XmlUtils.nextElement(parser);
  21. String name = parser.getName();
  22. if (! "voicemail".equals(name)) {
  23. break;
  24. }
  25. String[] data = new String[SIZE];
  26. //获取预置的VoiceMail信息
  27. String numeric = parser.getAttributeValue( null, "numeric");
  28. data[NAME] = parser.getAttributeValue( null, "carrier");
  29. data[NUMBER] = parser.getAttributeValue( null, "vmnumber");
  30. data[TAG] = parser.getAttributeValue( null, "vmtag");
  31. //保存在CarrierVmMap的缓存中
  32. CarrierVmMap.put(numeric, data);
  33. }
  34. } catch (XmlPullParserException e) {
  35. } catch (IOException e) {
  36. } finally {
  37. }
  38. }
        在VoiceMailConstants的创建过程中,解析系统"etc/voicemail-conf.xml"文件,并把里面的每一项(包含numeric、carrier、vmnumber、vmtag信息)都保存在CarrierVmMap缓存中,以便查询。
        然后我们来看什么情况下会用该缓存设置VoiceMail:
        在fetchSimRecords()更新SIMRecords的过程中,需要向Modem请求多次SIM卡信息的请求,每发送一次都会记录下发送的数目:
  1. protected void fetchSimRecords() {
  2. mCi.getIMSIForApp(mParentApp.getAid(), obtainMessage(EVENT_GET_IMSI_DONE));
  3. //记录下发送请求的个数
  4. mRecordsToLoad++;
  5. }
        然后在handleMessage()的finally中,会将当前已经处理过的Event数目减掉:
  1. public void handleMessage(Message msg) {
  2. try {
  3. switch (msg.what) {
  4. }
  5. } catch (RuntimeException exc) {
  6. } finally {
  7. //每个曾加+1的Event接收到回应后都会是isRecordLoadResponse==true
  8. if (isRecordLoadResponse) {
  9. onRecordLoaded();
  10. }
  11. }
  12. }
  13. protected void onRecordLoaded() {
  14. //当该Event处理完毕后,要将mRecordsToLoad数目-1
  15. mRecordsToLoad -= 1;
  16. if (mRecordsToLoad == 0 && mRecordsRequested == true) {
  17. //表明所有Event处理完毕
  18. onAllRecordsLoaded();
  19. } else if (mRecordsToLoad < 0) {
  20. mRecordsToLoad = 0;
  21. }
  22. }
        也就是说,当请求的所有Event都处理完毕后,就会进入onAllRecordsLoaded()中继续处理:
  1. protected void onAllRecordsLoaded() {
  2. //得到当前的MCC+MNC
  3. String operator = getOperatorNumeric();
  4. //根据的当前的MCC+MNC去配置文件中查找相应的VoiceMail
  5. setVoiceMailByCountry(operator);
  6. }
        继续看:
  1. private void setVoiceMailByCountry (String spn) {
  2. if (mVmConfig.containsCarrier(spn)) {
  3. mIsVoiceMailFixed = true;
  4. //读取XML中的VoiceMail信息
  5. mVoiceMailNum = mVmConfig.getVoiceMailNumber(spn);
  6. mVoiceMailTag = mVmConfig.getVoiceMailTag(spn);
  7. }
  8. }

        到这里我们发现,最终是通过当前SIM卡的MCC+MNC去配置文件中匹配相应的VoiceMail信息。


4.3、设置VoiceMail信息


        用户可以在手机的通话设置中更改当前的VoiceMail信息,当用户修改完成点击确认之后,就会调用到setVoiceMailNumber()方法,向Modem发送请求,这里需要注意,如果当前的VoiceMail信息是从配置文件中读取时,用户是无法修改VoiceMail的(这种情况说明,该运营商的语音信箱号码是固定的,无需修改)。而且设置VoiceMail信息时需要区分当前VoiceMail所在的分区信息。
  1. public void setVoiceMailNumber(String alphaTag, String voiceNumber, Message onComplete) {
  2. if (mIsVoiceMailFixed) {
  3. //从配置文件中读取的VoiceMail无法修改
  4. AsyncResult.forMessage((onComplete)).exception = new IccVmFixedException( "Voicemail number is fixed by operator");
  5. onComplete.sendToTarget();
  6. return;
  7. }
  8. mNewVoiceMailNum = voiceNumber;
  9. mNewVoiceMailTag = alphaTag;
  10. AdnRecord adn = new AdnRecord(mNewVoiceMailTag, mNewVoiceMailNum);
  11. if (mMailboxIndex != 0 && mMailboxIndex != 0xff) {
  12. //向EF_MBDN分区更新Voicemail
  13. new AdnRecordLoader(mFh).updateEF(adn, EF_MBDN, EF_EXT6, mMailboxIndex, null, obtainMessage(EVENT_SET_MBDN_DONE, onComplete));
  14. } else if (isCphsMailboxEnabled()) {
  15. //向EF_MAILBOX_CPHS分区更新Voicemail
  16. new AdnRecordLoader(mFh).updateEF(adn, EF_MAILBOX_CPHS, EF_EXT1, 1, null, obtainMessage(EVENT_SET_CPHS_MAILBOX_DONE, onComplete));
  17. } else {
  18. //异常处理
  19. AsyncResult.forMessage((onComplete)).exception = new IccVmNotSupportedException( "Update SIM voice mailbox error");
  20. onComplete.sendToTarget();
  21. }
  22. }
        更新完成之后就会在handleMessage()中更新mVoiceMailNum、mVoiceMailTag的值:
  1. public void handleMessage(Message msg) {
  2. switch (msg.what) {
  3. case EVENT_SET_MBDN_DONE:
  4. //EF_MBDN分区的更新结果
  5. isRecordLoadResponse = false;
  6. ar = (AsyncResult)msg.obj;
  7. if (ar.exception == null) {
  8. //重新设置mVoiceMailNum、mVoiceMailTag的值
  9. mVoiceMailNum = mNewVoiceMailNum;
  10. mVoiceMailTag = mNewVoiceMailTag;
  11. }
  12. if (isCphsMailboxEnabled()) {
  13. adn = new AdnRecord(mVoiceMailTag, mVoiceMailNum);
  14. Message onCphsCompleted = (Message) ar.userObj;
  15. if (ar.exception == null && ar.userObj != null) {
  16. AsyncResult.forMessage(((Message) ar.userObj)).exception = null;
  17. //给申请者发送更改成功的回调信息
  18. ((Message) ar.userObj).sendToTarget();
  19. onCphsCompleted = null;
  20. }
  21. //更新AdnRecord
  22. new AdnRecordLoader(mFh). updateEF(adn, EF_MAILBOX_CPHS, EF_EXT1, 1, null,
  23. obtainMessage(EVENT_SET_CPHS_MAILBOX_DONE,
  24. onCphsCompleted));
  25. } else {
  26. if (ar.userObj != null) {
  27. AsyncResult.forMessage(((Message) ar.userObj)).exception = ar.exception;
  28. //给申请者发送更改失败的回调信息
  29. ((Message) ar.userObj).sendToTarget();
  30. }
  31. }
  32. break;
  33. case EVENT_SET_CPHS_MAILBOX_DONE:
  34. //EF_MAILBOX_CPHS分区的更新结果
  35. isRecordLoadResponse = false;
  36. ar = (AsyncResult)msg.obj;
  37. if(ar.exception == null) {
  38. //重新设置mVoiceMailNum、mVoiceMailTag的值
  39. mVoiceMailNum = mNewVoiceMailNum;
  40. mVoiceMailTag = mNewVoiceMailTag;
  41. } else {
  42. if (DBG) log( "Set CPHS MailBox with exception: " + ar.exception);
  43. }
  44. if (ar.userObj != null) {
  45. //给申请者发送更改的结果
  46. AsyncResult.forMessage(((Message) ar.userObj)).exception = ar.exception;
  47. ((Message) ar.userObj).sendToTarget();
  48. }
  49. break;
  50. }
  51. }
        由此,我们就完成了更改Voicemail信息的任务。
        下一章节将介绍 CatService相关知识。

猜你喜欢

转载自blog.csdn.net/zx249388847/article/details/80852494