SubscriptionInfo是啥

frameworks/base/telephony/java/android/telephony/SubscriptionInfo.java

public class SubscriptionInfo implements Parcelable {
实现Parcelable是为了进程间ipc。
 @Override    public void writeToParcel(Parcel dest, int flags) {        dest.writeInt(mId);   //数据库id,递增主键,每一个iccid的卡会占用1个id        dest.writeString(mIccId);  //sim卡的iccid,每张sim卡是唯一的        dest.writeInt(mSimSlotIndex);  //sim卡插入卡槽值,0是卡1,1是卡2,没有插入则是-1        dest.writeCharSequence(mDisplayName); //sim卡名称,用户可以自定义        dest.writeCharSequence(mCarrierName); //运营商名称        dest.writeInt(mNameSource);  //名称来源,是用户设置或者是从sim卡读取(一般就是运营商名称)等        dest.writeInt(mIconTint);   //sim卡图标染色值,tint的概念可以百度google        dest.writeString(mNumber);  //sim卡关联号码        dest.writeInt(mDataRoaming);  //sim卡是否启用数据漫游        dest.writeInt(mMcc);    //mcc,移动国家码,3位数字,中国是460        dest.writeInt(mMnc);    //mnc,移动网络码,2位数字,如00,01等,表示运营商        dest.writeString(mCountryIso); //国家iso代码         mIconBitmap.writeToParcel(dest, flags);  //sim卡图标    }
SubscriptionInfo各个字段见代码中所加的注释。它是和TelephonyProvider数据库中的siminfo对应的。最常用的字段就是mSimSlotIndex和mId,见 subid和slotid,这里的mSimSlotIndex对应slotid,mId对应subid

packages/providers/TelephonyProvider/src/com/android/providers/telephony/TelephonyProvider.java

        private void createSimInfoTable(SQLiteDatabase db) {            if (DBG) log("dbh.createSimInfoTable:+");            db.execSQL("CREATE TABLE " + SIMINFO_TABLE + "("                    + SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + " INTEGER PRIMARY KEY AUTOINCREMENT,"                    + SubscriptionManager.ICC_ID + " TEXT NOT NULL,"                    + SubscriptionManager.SIM_SLOT_INDEX + " INTEGER DEFAULT " + SubscriptionManager.SIM_NOT_INSERTED + ","                    + SubscriptionManager.DISPLAY_NAME + " TEXT,"                    + SubscriptionManager.CARRIER_NAME + " TEXT,"                    + SubscriptionManager.NAME_SOURCE + " INTEGER DEFAULT " + SubscriptionManager.NAME_SOURCE_DEFAULT_SOURCE + ","                    + SubscriptionManager.COLOR + " INTEGER DEFAULT " + SubscriptionManager.COLOR_DEFAULT + ","                    + SubscriptionManager.NUMBER + " TEXT,"                    + SubscriptionManager.DISPLAY_NUMBER_FORMAT + " INTEGER NOT NULL DEFAULT " + SubscriptionManager.DISPLAY_NUMBER_DEFAULT + ","                    + SubscriptionManager.DATA_ROAMING + " INTEGER DEFAULT " + SubscriptionManager.DATA_ROAMING_DEFAULT + ","                    + SubscriptionManager.MCC + " INTEGER DEFAULT 0,"                    + SubscriptionManager.MNC + " INTEGER DEFAULT 0,"                    ...                    + SubscriptionManager.CB_OPT_OUT_DIALOG + " INTEGER DEFAULT 1"                    + ");");            if (DBG) log("dbh.createSimInfoTable:-");        }
建表函数createSimInfoTable,常量都是在SubscriptionManager中定义,可以从名字看出和SubscriptionInfo是对应的,当然后面mtk加了不少字段

SubscriptionInfo就是代表了sim卡的相关数据

SubscriptionInfo的更新

frameworks/opt/telephony/src/java/com/android/internal/telephony/PhoneFactory.java

    public static void makeDefaultPhone(Context context) {               ...                SubscriptionController.getInstance().updatePhonesAvailability((PhoneProxy[]) sProxyPhones);               Rlog.i(LOG_TAG, "Creating SubInfoRecordUpdater ");               sSubInfoRecordUpdater = new SubscriptionInfoUpdater(context,                       sProxyPhones, sCommandsInterfaces);               SubscriptionController.getInstance().updatePhonesAvailability(sProxyPhones);               ...    }
PhoneFactory的makeDefaultPhone是com.android.phone进程初始化的起点,和subscription相关的有SubscriptionController和SubscriptionInfoUpdater

frameworks/opt/telephony/src/java/com/android/internal/telephony/SubscriptionController.java

    private SubscriptionController(Phone phone) {        ...        if(ServiceManager.getService("isub") == null) {                ServiceManager.addService("isub", this);        }        if (DBG) logdl("[SubscriptionController] init by Phone");    }

SubscriptionController构造函数中添加了isub系统服务,这个服务后续会用到。

frameworks/opt/telephony/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java

public class SubscriptionInfoUpdater extends Handler
继承自Handler,方便消息处理,该类中更新信息的主要方法是updateSubscriptionInfoByIccId

     synchronized private void updateSubscriptionInfoByIccId() {               ...                contentResolver.update(SubscriptionManager.CONTENT_URI, value,                            SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "="                            + Integer.toString(oldSubInfo.get(0).getSubscriptionId()), null);               ...               mSubscriptionManager.addSubscriptionInfoRecord(mIccId[i], i);               ...    }

该函数非常长,这里只选取update和insert的部分代码展示流程,没有插入过卡当新卡,已经插入过的卡更新数据。插入与否是通过数据库中是否有其iccid记录判断的.更新的逻辑是更新slot值,没有插入的卡会设置卡槽值为-1,插入的卡会设置为0或者1。SubscriptionManager.CONTENT_URI对应于之前提到的TelephonyProvider数据库中的siminfo表uri。

frameworks/base/telephony/java/android/telephony/SubscriptionManager.java

   public Uri addSubscriptionInfoRecord(String iccId, int slotId) {        ...        try {            ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));            if (iSub != null) {                // FIXME: This returns 1 on success, 0 on error should should we return it?                iSub.addSubInfoRecord(iccId, slotId);            }        } catch (RemoteException ex) {            // ignore it        }        // FIXME: Always returns null?        return null;    }

插入新卡调用了isub系统服务中的addSubInfoRecord方法,isub之前讲过实现类就是SubscriptionController。

    public int addSubInfoRecord(String iccId, int slotId) {        ...        setDisplayName = true;        ContentValues value = new ContentValues();        value.put(SubscriptionManager.ICC_ID, iccId);        // default SIM color differs between slots        value.put(SubscriptionManager.COLOR, color);        value.put(SubscriptionManager.SIM_SLOT_INDEX, slotId);        value.put(SubscriptionManager.CARRIER_NAME, "");        Uri uri = resolver.insert(SubscriptionManager.CONTENT_URI, value);        ...}

这个函数也是非常长的,只截取了插入数据的那部分代码

SubscriptionInfo的更新调用链

这小节展示updateSubscriptionInfoByIccId是怎么被调用的

1.UiccController

frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/UiccController.java
 private UiccController(Context c, CommandsInterface []ci) {        ...        mCis[i].registerForIccStatusChanged(this, EVENT_ICC_STATUS_CHANGED, index);        ...    }
构造函数中向ril注册了EVENT_ICC_STATUS_CHANGED消息,监听sim卡状态变化。
  case EVENT_ICC_STATUS_CHANGED:                mCis[index].getIccCardStatus(obtainMessage(                                EVENT_GET_ICC_STATUS_DONE, index));
sim状态有变化的时候会发送EVENT_GET_ICC_STATUS_DONE消息到UiccController的消息处理方法HandlerMessage中,UiccController本身就是继承自Handler,telephony framework中需要使用消息的基本都用了继承Handler这种方式。该消息处理中使用了ril的getIccCardStatus方法,并传递EVENT_GET_ICC_STATUS_DONE消息做为参数,这个也是ril中使用率很高的技术,方法处理完毕ril会上报该消息回来引起回调。
               case EVENT_GET_ICC_STATUS_DONE:                    if (DBG) log("Received EVENT_GET_ICC_STATUS_DONE");                    onGetIccCardStatusDone(ar, index);                    break;
EVENT_GET_ICC_STATUS_DONE会调用onGetIccCardStatusDone方法。
private synchronized void onGetIccCardStatusDone(AsyncResult ar, Integer index) {    ...   mIccChangedRegistrants.notifyRegistrants(new AsyncResult(null, index, null));   ...}
 public void registerForIccChanged(Handler h, int what, Object obj) {        synchronized (mLock) {            Registrant r = new Registrant (h, what, obj);            mIccChangedRegistrants.add(r);            //Notify registrant right after registering, so that it will get the latest ICC status,            //otherwise which may not happen until there is an actual change in ICC status.            r.notifyRegistrant();        }    }
mIccChangedRegistrants是会通知所有注册到它这里的handler,发送的消息值是注册时候传递进的what,参数是注册时传递进去的obj。

2.IccCardProxy

frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/IccCardProxy.java
  public IccCardProxy(Context context, CommandsInterface ci, int phoneId) {        ...        mUiccController.registerForIccChanged(this, EVENT_ICC_CHANGED, null);        ...}
IccCardProxy构造函数中就向UiccController注册了监听卡变化的事件,UiccController的上报流程已经分析过了。
case EVENT_ICC_CHANGED:    ...    updateIccAvailability();    ...
它同样继承自Handler,收到消息后调用updateIccAvailability
private void updateIccAvailability() {        ...registerUiccCardEvents();        ...}
   private void registerUiccCardEvents() {        ...mIccRecords.registerForRecordsLoaded(this, EVENT_RECORDS_LOADED, null);        ...}
updateIccAvailability在必要的时候调用registerUiccCardEvents,然后注册了EVENT_RECORDS_LOADED消息,该消息上报流程比较复杂,见第三小节,这里先看该消息的处理:
case EVENT_RECORDS_LOADED:    ...    onRecordsLoaded()    ...
    private void onRecordsLoaded() {        broadcastInternalIccStateChangedIntent(IccCardConstants.INTENT_VALUE_ICC_LOADED, null);    }
EVENT_RECORDS_LOADED消息处理中最后会发送一个广播:
   private void broadcastInternalIccStateChangedIntent(String value, String reason) {             ...            Intent intent = new Intent(ACTION_INTERNAL_SIM_STATE_CHANGED);            ...            ActivityManagerNative.broadcastStickyIntent(intent, null, UserHandle.USER_ALL);   }

3.SimRecords

这节解析EVENT_RECORDS_LOADED上报流程frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/SIMRecords.java
public SIMRecords(UiccCardApplication app, Context c, CommandsInterface ci) {   ...   mCi.registerForIccRefresh(this, EVENT_SIM_REFRESH, null);   ...}
构造方法向ril注册EVENT_SIM_REFRESH消息
            case EVENT_SIM_REFRESH:                ...                handleSimRefresh((IccRefreshResponse)ar.result);                ...
消息处理:
private void handleSimRefresh(IccRefreshResponse refreshResponse){    ...    handleFileUpdate(refreshResponse.efId[i]);    ...}
private void handleFileUpdate(int efid) {    ...     fetchSimRecords();    ...}
protected void fetchSimRecords() {    ...    getIccIdRecord();    ...}
protected void getIccIdRecord() {    sendMessage(obtainMessage(EVENT_GET_ICCID));}
依次调用,注意getIccIdRecord方法是基类中的方法。
 case EVENT_GET_ICCID:        ...        isRecordLoadResponse = true;        ...     if (isRecordLoadResponse) {         onRecordLoaded();     }
EVENT_GET_ICCID消息处理中isRecordLoadResponse 会被设置为true,这样最后会调用onRecordLoaded。isRecordLoadResponse 设置为true不止有这一条路径,其它路径也是类似的流程
protected void onRecordLoaded() {    ...    onAllRecordsLoaded();    ...}
protected void onAllRecordsLoaded() {    ...    mRecordsLoadedRegistrants.notifyRegistrants(           new AsyncResult(null, null, null));    ...}
mRecordsLoadedRegistrants会通知所有注册的handler,这样EVENT_RECORDS_LOADED消息就会发送到IccCardProxy的消息处理函数中

4.SubscriptionInfoUpdater

frameworks/opt/telephony/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
回到第二小节末尾,讲到发送了个广播。SubscriptionInfoUpdater构造函数中注册了IccCardProxy.ACTION_INTERNAL_SIM_STATE_CHANGED广播接受器接收这个广播:
public SubscriptionInfoUpdater(Context context, Phone[] phoneProxy, CommandsInterface[] ci) {     ...     IntentFilter intentFilter = new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);     intentFilter.addAction(IccCardProxy.ACTION_INTERNAL_SIM_STATE_CHANGED);     ...     mContext.registerReceiver(sReceiver, intentFilter);     ...}
这样SubscriptionInfoUpdater收到通知的链条就搭起来了。
    private final BroadcastReceiver sReceiver = new  BroadcastReceiver() {        @Override        public void onReceive(Context context, Intent intent) {            ...            } else if (action.equals(IccCardProxy.ACTION_INTERNAL_SIM_STATE_CHANGED)) {                ...                } else if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(simStatus)) {                    sendMessage(obtainMessage(EVENT_SIM_LOADED, slotId, -1));            ...     }
广播处理中发送EVENT_SIM_LOADED消息,SubscriptionInfoUpdater同样是继承自Handler:
       case EVENT_SIM_LOADED: {                ...                SubscriptionUpdatorThread updatorThread = new SubscriptionUpdatorThread(                        new QueryIccIdUserObj(null, msg.arg1),                        SubscriptionUpdatorThread.SIM_LOADED);                updatorThread.start();
消息处理中用SubscriptionUpdatorThread开线程继续处理:
    public void run() {            switch (mEventId) {                case SIM_ABSENT:                    handleSimAbsent(mUserObj.slotId);                    break;                case SIM_LOADED:                    handleSimLoaded(mUserObj.slotId);                    break;            ...    }
线程run方法中针对插卡或者拔卡不同分支处理,现只看插卡:
 private void handleSimLoaded(int slotId) {        ...        if (isAllIccIdQueryDone() && needUpdate) {        // MTK-END            updateSubscriptionInfoByIccId();        }        ...}
终于走到了终点

双卡相关类的关系

frameworks/base/telephony/java/android/telephony

SubscriptionInfo.aidl和SubscriptionInfo.java,文章开始已经讲述过
SubscriptionManager.java 为三方app层使用,可以获取和设置当前双卡设置(如当前默认拨号卡);可以进行slotid和subId转换等;可以获取当前的卡信息SubscriptionInfo。它的功能基本都是通过binder调用SubscriptionController服务端来实现。
ISub.aidl  SubscriptionManager使用它和SubscriptionController进行沟通:

public class SubscriptionController extends ISub.Stub {
ISubscriptionListener.aidl 有和IOnSubscriptionsChangedListener类似的方法,但是搜索下没见有地方使用这个类。
IOnSubscriptionsChangedListener.aidl 使用它的地方在SubscriptionManager:

  public void addOnSubscriptionsChangedListener(OnSubscriptionsChangedListener listener) {        String pkgForDebug = mContext != null ? mContext.getOpPackageName() : "";        if (DBG) {            logd("register OnSubscriptionsChangedListener pkgForDebug=" + pkgForDebug                    + " listener=" + listener);        }        try {            // We use the TelephonyRegistry as it runs in the system and thus is always            // available. Where as SubscriptionController could crash and not be available            ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(                    "telephony.registry"));            if (tr != null) {                tr.addOnSubscriptionsChangedListener(pkgForDebug, listener.callback);            }        } catch (RemoteException ex) {            // Should not happen        }    }

可以使用它监听卡信息的变化,看到TelephonyRegistry就能想到它的整个流程原理类似PhoneStateListener。

frameworks/opt/telephony

SubscriptionController.java 运行在phone进程中,是双卡相关功能正真实现端,为SubscriptionManager提供服务。
SubscriptionInfoUpdater.java 运行在phone进程中,监听ril上报事件并更新siminfo表。

Siminfo和SubscriptionManager历史

Android5.0之前源码是没有双卡机制的,双卡代码是各个厂商自己写。siminfo是mtk引入的,而SubscriptionManager是高通引入的。google结合了这两者产生了目前这么复杂的双卡代码,原本各自是没有现在这么复杂的:mtk写的siminfo更新没有这么麻烦,而且siminfo是为了显示sim卡相关信息,日常双卡相关代码是按卡槽走,基本不用siminfo表,双卡设置中开启和关闭sim卡的代码和siminfo也没啥大关系;高通的SubscriptionManager没有subid这个概念,也是只有卡槽的概念,主要的功能是为了双卡设置中开启和关闭sim卡。

不过幸好google统一了代码,各个厂商的通信模块多卡相关代码不再会有那么大的差异了,不过目前4G、视频通话、通话录音等等相关google没有统一,所以差异还是非常大呀


更多相关文章

  1. [Android]代码实现StateListDrawable
  2. Android仿计算器布局代码
  3. linux和windows平台下下载android sdk的源代码【Z】
  4. Android Framework分析 ---- 1消息处理机制 java层
  5. Android的消息机制,用Android线程间通信的Message机制,Android中Ha
  6. Android获取本机电话号码的简单方法
  7. 如何在eclipse的android工程里引用android sdk之外的类和方法

随机推荐

  1. Struts消息国际化及异常处理
  2. 在ios中,html5页面打电话
  3. flex嵌入jsp中遇到的难题?
  4. 将我的代码中的JavaDoc注释转换为HTML
  5. 网页语言有html,php.jsp,无论什么语言浏览
  6. confirm 确认框的一个实际应用
  7. Bootstrap glyphicons未在IE和Safari中显
  8. WebWork2教程(中文版)(4.1.1)
  9. html网页制作小试
  10. 如何实现图片的动态切换(间隔几秒就切换下