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卡的相关数据
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);
...
}
这个函数也是非常长的,只截取了插入数据的那部分代码
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。
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);
}
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的消息处理函数中
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();
}
...
}
终于走到了终点
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类似的方法,但是搜索下没见有地方使用这个类。
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。
SubscriptionController.java 运行在phone进程中,是双卡相关功能正真实现端,为SubscriptionManager提供服务。
SubscriptionInfoUpdater.java 运行在phone进程中,监听ril上报事件并更新siminfo表。
Android5.0之前源码是没有双卡机制的,双卡代码是各个厂商自己写。siminfo是mtk引入的,而SubscriptionManager是高通引入的。google结合了这两者产生了目前这么复杂的双卡代码,原本各自是没有现在这么复杂的:mtk写的siminfo更新没有这么麻烦,而且siminfo是为了显示sim卡相关信息,日常双卡相关代码是按卡槽走,基本不用siminfo表,双卡设置中开启和关闭sim卡的代码和siminfo也没啥大关系;高通的SubscriptionManager没有subid这个概念,也是只有卡槽的概念,主要的功能是为了双卡设置中开启和关闭sim卡。
不过幸好google统一了代码,各个厂商的通信模块多卡相关代码不再会有那么大的差异了,不过目前4G、视频通话、通话录音等等相关google没有统一,所以差异还是非常大呀