Android N SIM卡 SubscriptionInfo 添加与维护

时间:2022-03-29 14:06:15
前面我们说到SIM卡状态变化会由 ICCCardProxy 发出广播,通知接收者进行各自逻辑处理。SubInfoRecordUpdater 就是通过接收SIM卡状态变化广播来实现 SubscriptionInfo 的添加与维护。此外,还有两个关键类 SubscriptionManager 和 SubscriptionController,它们通过各种接口来向外提供 SubInfo 信息的查询和修改。

一、检卡后 subInfo 的首次加载与更新
查看代码,SubInfoRecordUpdater 的创建是在 PhoneFactory -- makeDefaultPhone 中创建完 Phone 实例后进行初始化,构造函数如下
frameworks/opt/telephony/src/java/com/android/internal/telephony/SubscriptionInfoUpdater.java
    public SubscriptionInfoUpdater(Context context, Phone[] phone, CommandsInterface[] ci) {
logd("Constructor invoked");

mContext = context;
mPhone = phone;
mSubscriptionManager = SubscriptionManager.from(mContext);
mPackageManager = IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
// SIM 卡状态变化、系统语言变化等广播注册监听
IntentFilter intentFilter = new IntentFilter(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
intentFilter.addAction(IccCardProxy.ACTION_INTERNAL_SIM_STATE_CHANGED);
intentFilter.addAction(Intent.ACTION_USER_UNLOCKED);
intentFilter.addAction(Intent.ACTION_LOCALE_CHANGED);
mContext.registerReceiver(sReceiver, intentFilter);

mCarrierServiceBindHelper = new CarrierServiceBindHelper(mContext);
initializeCarrierApps();
resetSimidforSIM(); // LAFITEN-170:zhaojian at 20161109
}

卡状态变化时,根据当前的 simStatus 发出不同 EVENT 便于后续处理
            if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) {
rebroadcastIntentsOnUnlock.put(slotId, intent);
if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(simStatus)) {
sendMessage(obtainMessage(EVENT_SIM_ABSENT, slotId, -1));
} else if (IccCardConstants.INTENT_VALUE_ICC_UNKNOWN.equals(simStatus)) {
sendMessage(obtainMessage(EVENT_SIM_UNKNOWN, slotId, -1));
} else if (IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR.equals(simStatus)) {
sendMessage(obtainMessage(EVENT_SIM_IO_ERROR, slotId, -1));
} else if (IccCardConstants.INTENT_VALUE_ICC_CARD_RESTRICTED.equals(simStatus)) {
sendMessage(obtainMessage(EVENT_SIM_RESTRICTED, slotId, -1));
} else {
logd("Ignoring simStatus: " + simStatus);
}
} else if (action.equals(IccCardProxy.ACTION_INTERNAL_SIM_STATE_CHANGED)) {
if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(simStatus)) {
String reason = intent.getStringExtra(
IccCardConstants.INTENT_KEY_LOCKED_REASON);
sendMessage(obtainMessage(EVENT_SIM_LOCKED, slotId, -1, reason));
} else if (IccCardConstants.INTENT_VALUE_ICC_LOADED.equals(simStatus)) {
sendMessage(obtainMessage(EVENT_SIM_LOADED, slotId, -1));
} else {
logd("Ignoring simStatus: " + simStatus);
}
}


对于 SubscriptionInfo 的更新初始入口为 updateSubscriptionInfoByIccId 函数,从代码看关联的 EVENT 主要有 4 处
   public void handleMessage(Message msg) {
switch (msg.what) {
case EVENT_SIM_LOCKED_QUERY_ICCID_DONE: {
AsyncResult ar = (AsyncResult)msg.obj;
QueryIccIdUserObj uObj = (QueryIccIdUserObj) ar.userObj;
int slotId = uObj.slotId;
logd("handleMessage : <EVENT_SIM_LOCKED_QUERY_ICCID_DONE> SIM" + (slotId + 1));
if (ar.exception == null) {
if (ar.result != null) {
byte[] data = (byte[])ar.result;
//Telephony:change iccid parse function, process the iccid with 'f'.
//mIccId[slotId] = IccUtils.bcdToString(data, 0, data.length);
mIccId[slotId] = IccUtils.parseIccIdToString(data, 0, data.length);
} else {
logd("Null ar");
mIccId[slotId] = ICCID_STRING_FOR_NO_SIM;
}
} else {
mIccId[slotId] = ICCID_STRING_FOR_NO_SIM;
logd("Query IccId fail: " + ar.exception);
}
logd("sIccId[" + slotId + "] = " + mIccId[slotId]);
if (isAllIccIdQueryDone()) {
updateSubscriptionInfoByIccId();
}
broadcastSimStateChanged(slotId, IccCardConstants.INTENT_VALUE_ICC_LOCKED,
uObj.reason);
if (!ICCID_STRING_FOR_NO_SIM.equals(mIccId[slotId])) {
updateCarrierServices(slotId, IccCardConstants.INTENT_VALUE_ICC_LOCKED);
}
break;
}

case EVENT_SIM_LOADED:
handleSimLoaded(msg.arg1);
break;

case EVENT_SIM_ABSENT:
handleSimAbsentOrError(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_ABSENT);
break;

case EVENT_SIM_IO_ERROR:
handleSimAbsentOrError(msg.arg1, IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR);
break;
......
}
}

关键调用逻辑为
                if (isAllIccIdQueryDone()) {
updateSubscriptionInfoByIccId();
}

其中 isAllIccIdQueryDone 对于设置默认业务卡等是一个非常关键的判断,也是经常出问题的地方
    protected boolean isAllIccIdQueryDone() {
for (int i = 0; i < PROJECT_SIM_NUM; i++) {
if (mIccId[i] == null) {
logd("Wait for SIM" + (i + 1) + " IccId");
return false;
}
}
logd("All IccIds query complete");

return true;
}


PS: 从现有代码逻辑,对 mIccId 赋值情形如下,这里在双卡检卡快慢差异等复杂场景下,会出现热插拔导致默认业务卡设置异常情形,需要注意
① New              mIccid = null
② Absent/Error     mIccId[slotId] = ICCID_STRING_FOR_NO_SIM,即 “”
③ Load             mIccId[slotId] = records.getIccId()  ,此处 IccRecords records = mPhone[slotId].getIccCard().getIccRecords()
④ Locked           mIccId[slotId] = null --> EVENT_SIM_LOCKED_QUERY_ICCID_DONE --> mIccId[slotId] = IccUtils.parseIccIdToString,
                   无返回结果或异常时 mIccId[slotId] = ICCID_STRING_FOR_NO_SIM


下面我们接着看下更新 subInfo 的关键处理函数
   /**
* TODO: Simplify more, as no one is interested in what happened
* only what the current list contains.
*/
synchronized protected void updateSubscriptionInfoByIccId() {
logd("updateSubscriptionInfoByIccId:+ Start");
// 清除对应关系MAP -- sSlotIdxToSubId
mSubscriptionManager.clearSubscriptionInfo();
// 设置插卡状态
for (int i = 0; i < PROJECT_SIM_NUM; i++) {
mInsertSimState[i] = SIM_NOT_CHANGE;
}

int insertedSimCount = PROJECT_SIM_NUM;
for (int i = 0; i < PROJECT_SIM_NUM; i++) {
if (ICCID_STRING_FOR_NO_SIM.equals(mIccId[i])) {
insertedSimCount--;
mInsertSimState[i] = SIM_NOT_INSERT;
}
}
logd("insertedSimCount = " + insertedSimCount);

int index = 0;
for (int i = 0; i < PROJECT_SIM_NUM; i++) {
if (mInsertSimState[i] == SIM_NOT_INSERT) {
continue;
}
index = 2;
for (int j = i + 1; j < PROJECT_SIM_NUM; j++) {
if (mInsertSimState[j] == SIM_NOT_CHANGE && mIccId[i].equals(mIccId[j])) {
mInsertSimState[i] = 1;
mInsertSimState[j] = index;
index++;
}
}
}

ContentResolver contentResolver = mContext.getContentResolver();
String[] oldIccId = new String[PROJECT_SIM_NUM];
for (int i = 0; i < PROJECT_SIM_NUM; i++) {
oldIccId[i] = null;
List<SubscriptionInfo> oldSubInfo =
SubscriptionController.getInstance().getSubInfoUsingSlotIdWithCheck(i, false,
mContext.getOpPackageName());
if (oldSubInfo != null) {
oldIccId[i] = oldSubInfo.get(0).getIccId();
logd("updateSubscriptionInfoByIccId: oldSubId = "
+ oldSubInfo.get(0).getSubscriptionId());
if (mInsertSimState[i] == SIM_NOT_CHANGE && !mIccId[i].equals(oldIccId[i])) {
mInsertSimState[i] = SIM_CHANGED;
}
if (mInsertSimState[i] != SIM_NOT_CHANGE) {
ContentValues value = new ContentValues(1);
value.put(SubscriptionManager.SIM_SLOT_INDEX,
SubscriptionManager.INVALID_SIM_SLOT_INDEX);
contentResolver.update(SubscriptionManager.CONTENT_URI, value,
SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "="
+ Integer.toString(oldSubInfo.get(0).getSubscriptionId()), null);
}
} else {
if (mInsertSimState[i] == SIM_NOT_CHANGE) {
// no SIM inserted last time, but there is one SIM inserted now
mInsertSimState[i] = SIM_CHANGED;
}
oldIccId[i] = ICCID_STRING_FOR_NO_SIM;
logd("updateSubscriptionInfoByIccId: No SIM in slot " + i + " last time");
}
}

//check if the inserted SIM is new SIM
int nNewCardCount = 0;
int nNewSimStatus = 0;
for (int i = 0; i < PROJECT_SIM_NUM; i++) {
if (mInsertSimState[i] == SIM_NOT_INSERT) {
logd("updateSubscriptionInfoByIccId: No SIM inserted in slot " + i + " this time");
} else {
// 调用 addSubscriptionInfoRecord 添加/更新 subInfo 数据库
if (mInsertSimState[i] > 0) {
//some special SIMs may have the same IccIds, add suffix to distinguish them
//FIXME: addSubInfoRecord can return an error.
mSubscriptionManager.addSubscriptionInfoRecord(mIccId[i]
+ Integer.toString(mInsertSimState[i]), i);
logd("SUB" + (i + 1) + " has invalid IccId");
} else /*if (sInsertSimState[i] != SIM_NOT_INSERT)*/ {
mSubscriptionManager.addSubscriptionInfoRecord(mIccId[i], i);
}
if (isNewSim(mIccId[i], oldIccId)) {
nNewCardCount++;
switch (i) {
case PhoneConstants.SUB1:
nNewSimStatus |= STATUS_SIM1_INSERTED;
break;
case PhoneConstants.SUB2:
nNewSimStatus |= STATUS_SIM2_INSERTED;
break;
case PhoneConstants.SUB3:
nNewSimStatus |= STATUS_SIM3_INSERTED;
break;
//case PhoneConstants.SUB3:
// nNewSimStatus |= STATUS_SIM4_INSERTED;
// break;
}

mInsertSimState[i] = SIM_NEW;
}
}
}

for (int i = 0; i < PROJECT_SIM_NUM; i++) {
if (mInsertSimState[i] == SIM_CHANGED) {
mInsertSimState[i] = SIM_REPOSITION;
}
logd("updateSubscriptionInfoByIccId: sInsertSimState[" + i + "] = "
+ mInsertSimState[i]);
}

List<SubscriptionInfo> subInfos = mSubscriptionManager.getActiveSubscriptionInfoList();
int nSubCount = (subInfos == null) ? 0 : subInfos.size();
logd("updateSubscriptionInfoByIccId: nSubCount = " + nSubCount);
for (int i=0; i < nSubCount; i++) {
SubscriptionInfo temp = subInfos.get(i);

String msisdn = TelephonyManager.getDefault().getLine1Number(
temp.getSubscriptionId());

if (msisdn != null) {
ContentValues value = new ContentValues(1);
value.put(SubscriptionManager.NUMBER, msisdn);
contentResolver.update(SubscriptionManager.CONTENT_URI, value,
SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "="
+ Integer.toString(temp.getSubscriptionId()), null);
}
}

// Ensure the modems are mapped correctly
mSubscriptionManager.setDefaultDataSubId(
mSubscriptionManager.getDefaultDataSubscriptionId());
// 通知 subInfo 变化
SubscriptionController.getInstance().notifySubscriptionInfoChanged();
logd("updateSubscriptionInfoByIccId:- SsubscriptionInfo update complete");
}


继续看下 addSubscriptionInfoRecord 的实现,这里通过 iSub 调到 SubscriptionController 实现方法 addSubInfoRecord
frameworks/opt/telephony/src/java/com/android/internal/telephony/SubscriptionController.java
    /**
* Add a new SubInfoRecord to subinfo database if needed
* @param iccId the IccId of the SIM card
* @param slotId the slot which the SIM is inserted
* @return 0 if success, < 0 on error.
*/
@Override
public int addSubInfoRecord(String iccId, int slotId) {

enforceModifyPhoneState("addSubInfoRecord");

// Now that all security checks passes, perform the operation as ourselves.
final long identity = Binder.clearCallingIdentity();
try {
if (iccId == null) {
if (DBG) logdl("[addSubInfoRecord]- null iccId");
return -1;
}
// 获取 subInfo 数据库存储 cursor,这里 CONTENT_URI = Uri.parse("content://telephony/siminfo")
ContentResolver resolver = mContext.getContentResolver();
Cursor cursor = resolver.query(SubscriptionManager.CONTENT_URI,
new String[]{SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID,
SubscriptionManager.SIM_SLOT_INDEX, SubscriptionManager.NAME_SOURCE},
SubscriptionManager.ICC_ID + "=?", new String[]{iccId}, null);

int color = getUnusedColor(mContext.getOpPackageName());
boolean setDisplayName = false;
try {
// 存在对应 iccId 数据库更新,不存在则插入
if (cursor == null || !cursor.moveToFirst()) {
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);
if (DBG) logdl("[addSubInfoRecord] New record created: " + uri);
} else {
int subId = cursor.getInt(0);
int oldSimInfoId = cursor.getInt(1);
int nameSource = cursor.getInt(2);
ContentValues value = new ContentValues();

if (slotId != oldSimInfoId) {
value.put(SubscriptionManager.SIM_SLOT_INDEX, slotId);
}

if (nameSource != SubscriptionManager.NAME_SOURCE_USER_INPUT) {
setDisplayName = true;
}

if (value.size() > 0) {
resolver.update(SubscriptionManager.CONTENT_URI, value,
SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID +
"=" + Long.toString(subId), null);
}

if (DBG) logdl("[addSubInfoRecord] Record already exists");
}
} finally {
if (cursor != null) {
cursor.close();
}
}

// 查询更新 sSlotIdxToSubId 中 slotId 和 subId 对应关系
cursor = resolver.query(SubscriptionManager.CONTENT_URI, null,
SubscriptionManager.SIM_SLOT_INDEX + "=?",
new String[] {String.valueOf(slotId)}, null);
try {
if (cursor != null && cursor.moveToFirst()) {
do {
int subId = cursor.getInt(cursor.getColumnIndexOrThrow(
SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID));
// If sSlotIdxToSubId already has a valid subId for a slotId/phoneId,
// do not add another subId for same slotId/phoneId.
Integer currentSubId = sSlotIdxToSubId.get(slotId);
if (currentSubId == null
|| !SubscriptionManager.isValidSubscriptionId(currentSubId)) {
// TODO While two subs active, if user deactivats first
// one, need to update the default subId with second one.

// FIXME: Currently we assume phoneId == slotId which in the future
// may not be true, for instance with multiple subs per slot.
// But is true at the moment.
sSlotIdxToSubId.put(slotId, subId);
int subIdCountMax = getActiveSubInfoCountMax();
int defaultSubId = getDefaultSubId();
// 更新默认业务卡
// Set the default sub if not set or if single sim device
if (!SubscriptionManager.isValidSubscriptionId(defaultSubId)
|| subIdCountMax == 1 || (!isActiveSubId(defaultSubId))) {
setDefaultFallbackSubId(subId);
}
// If single sim device, set this subscription as the default for everything
if (subIdCountMax == 1) {
if (DBG) {
logdl("[addSubInfoRecord] one sim set defaults to subId=" + subId);
}
setDefaultDataSubId(subId);
setDefaultSmsSubId(subId);
setDefaultVoiceSubId(subId);
}
} else {
if (DBG) {
logdl("[addSubInfoRecord] currentSubId != null"
+ " && currentSubId is valid, IGNORE");
}
}
if (DBG) logdl("[addSubInfoRecord] hashmap(" + slotId + "," + subId + ")");
} while (cursor.moveToNext());
}
} finally {
if (cursor != null) {
cursor.close();
}
}

// Set Display name after sub id is set above so as to get valid simCarrierName
int[] subIds = getSubId(slotId);
if (subIds == null || subIds.length == 0) {
if (DBG) {
logdl("[addSubInfoRecord]- getSubId failed subIds == null || length == 0 subIds="
+ subIds);
}
return -1;
}
if (setDisplayName) {
String simCarrierName = mTelephonyManager.getSimOperatorName(subIds[0]);
String nameToSet;

if (!TextUtils.isEmpty(simCarrierName)) {
nameToSet = simCarrierName;
} else {
nameToSet = "CARD " + Integer.toString(slotId + 1);
}

ContentValues value = new ContentValues();
value.put(SubscriptionManager.DISPLAY_NAME, nameToSet);
resolver.update(SubscriptionManager.CONTENT_URI, value,
SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID +
"=" + Long.toString(subIds[0]), null);

if (DBG) logdl("[addSubInfoRecord] sim name = " + nameToSet);
}

// Once the records are loaded, notify DcTracker
sPhones[slotId].updateDataConnectionTracker();

if (DBG) logdl("[addSubInfoRecord]- info size=" + sSlotIdxToSubId.size());

} finally {
Binder.restoreCallingIdentity(identity);
}
return 0;
}


至此,我们就完成了检卡完成后对 subInfo 的首次加载与更新。这里提一下 sSlotIdxToSubId,它是反应 slotId 和 SubId 对应关系的一组 ConcurrentHashMap,sSlotIdxToSubId.size() > 0 是 getActiveSubscriptionInfoList 和 getSubInfoUsingSlotIdWithCheck 等的前提
    private boolean isSubInfoReady() {
return sSlotIdxToSubId.size() > 0;
}


二、数据库中的 subInfo 信息
subInfo 存储数据库表 URL:content://telephony/siminfo,对应到的数据库为 telephony.db 中的 siminfo 表,如下为其对应的 createSimInfoTable,这里清晰地显示了对应的字段
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_EXTREME_THREAT_ALERT + " INTEGER DEFAULT 1,"
+ SubscriptionManager.CB_SEVERE_THREAT_ALERT + " INTEGER DEFAULT 1,"
+ SubscriptionManager.CB_AMBER_ALERT + " INTEGER DEFAULT 1,"
+ SubscriptionManager.CB_EMERGENCY_ALERT + " INTEGER DEFAULT 1,"
+ SubscriptionManager.CB_ALERT_SOUND_DURATION + " INTEGER DEFAULT 4,"
+ SubscriptionManager.CB_ALERT_REMINDER_INTERVAL + " INTEGER DEFAULT 0,"
+ SubscriptionManager.CB_ALERT_VIBRATE + " INTEGER DEFAULT 1,"
+ SubscriptionManager.CB_ALERT_SPEECH + " INTEGER DEFAULT 1,"
+ SubscriptionManager.CB_ETWS_TEST_ALERT + " INTEGER DEFAULT 0,"
+ SubscriptionManager.CB_CHANNEL_50_ALERT + " INTEGER DEFAULT 1,"
+ SubscriptionManager.CB_CMAS_TEST_ALERT + " INTEGER DEFAULT 0,"
+ SubscriptionManager.CB_OPT_OUT_DIALOG + " INTEGER DEFAULT 1"
+ ");");
if (DBG) log("dbh.createSimInfoTable:-");
}


这里我们介绍下它的几个主要字段
    /**
* TelephonyProvider unique key column name is the subscription id.
* 这个自不必说,android里的每个db都有的,是数据库里的数据主key,此处 _id 值也就是卡对应的 subId
* 是在数据库中保存时所在的条目的唯一标识。有多少条数据,就说明保存了多少个sim卡的信息;
*/
public static final String UNIQUE_KEY_SUBSCRIPTION_ID = "_id";

/**
* TelephonyProvider column name for SIM ICC Identifier
* 从卡上读取得到,是卡的唯一身份标识,世界上的所有sim卡,每个卡都有不同的iccid,就像身份证一样,如 89014103211118510720;
*/
public static final String ICC_ID = "icc_id";

/**
* TelephonyProvider column name for user SIM_SlOT_INDEX
* 分配给卡的id序号,android设计从0开始,最大为卡槽个数,比如对双卡终端,那么只有可能是0或者1;
* 当然无卡时其值为-1,所以这里其实是与卡槽固定对应的,卡槽如果有卡就取对应id,如果无卡,就设为-1;
*/
public static final String SIM_SLOT_INDEX = "sim_id";

/**
* TelephonyProvider column name for user displayed name.
* 分配给卡的显示名字,从代码上来看开机后会尝试使用运营商名字,如果取不到,就使用简单的SUB 01这样的字串表示,
* 等拿到运营商名字之后重新set,另外从下面的name_source字段的设计来看,android是允许用户来自己指定这个显示名的;
*/
public static final String DISPLAY_NAME = "display_name";

/**
* TelephonyProvider column name for the service provider name for the SIM.
* 运营商名字
*/
public static final String CARRIER_NAME = "carrier_name";

/**
* TelephonyProvider column name for source of the user displayed name.
* 表明 display_name 字段的来源,主要分4类:undefined (-1)、the default (0)、from the SIM (1)、from the user (2)
*/
public static final String NAME_SOURCE = "name_source";

/**
* TelephonyProvider column name for the color of a SIM.
* 显示颜色,将使用颜色在UI上明显区分卡1和卡2
*/
public static final String COLOR = "color";

/**
* TelephonyProvider column name for the phone number of a SIM.
* 卡对应的电话号码
*/
public static final String NUMBER = "number";

/**
* TelephonyProvider column name for the number display format of a SIM.
*/
public static final String DISPLAY_NUMBER_FORMAT = "display_number_format";

/**
* TelephonyProvider column name for permission for data roaming of a SIM.
* 是否允许这张卡进行数据漫游,默认禁止漫游,两个取值: enable (1),disable (0)
*/
public static final String DATA_ROAMING = "data_roaming";

/**
* TelephonyProvider column name for the MCC associated with a SIM.
* 移动国家码,从卡上读取得到
*/
public static final String MCC = "mcc";

/**
* TelephonyProvider column name for the MNC associated with a SIM.
* 移动网络码,从卡上读取得到
*/
public static final String MNC = "mnc";


三、subInfo 相关的主要工作类

1、SubscriptionInfo

SubInfo 的可序列化实现,封装数据库数据,构造函数如下 (A Parcelable class for Subscription Information)

    public SubscriptionInfo(int id, String iccId, int simSlotIndex, CharSequence displayName,
CharSequence carrierName, int nameSource, int iconTint, String number, int roaming,
Bitmap icon, int mcc, int mnc, String countryIso) {
this.mId = id;
this.mIccId = iccId;
this.mSimSlotIndex = simSlotIndex;
this.mDisplayName = displayName;
this.mCarrierName = carrierName;
this.mNameSource = nameSource;
this.mIconTint = iconTint;
this.mNumber = number;
this.mDataRoaming = roaming;
this.mIconBitmap = icon;
this.mMcc = mcc;
this.mMnc = mnc;
this.mCountryIso = countryIso;
}


2、TelephonyProvider
    
   siminfo数据库provider,直接操作DB,实现siminfo表的增删改查。

3、SubInfoRecordUpdater

   SIM卡状态变化的监听者,收到卡状态变化的广播后更新对应siminfo信息,如 iccid、displayname、phone number等。同时,SubInfoRecordUpdater 在其内部维护了各卡槽的卡状态,这里的卡状态区别于ACTION_SIM_STATE_CHANGED所携带的卡状态:ACTION_SIM_STATE_CHANGED的卡状态有 LOCKED、READY、NOT READY、ABSENT等,是指的卡的具体加载状态;而这里的卡状态是指 SIM_NOT_CHANGE、SIM_CHANGED、SIM_NEW、SIM_REPOSITION、SIM_NOT_INSERT,它们是在收到卡状态变化广播后,根据卡的iccid等一些信息算出来的。

4、SubscriptionManager
    
   该类是一个纯粹应用接口类,其内部并不实现具体的逻辑,只是通过调用SubscriptionController这个service的对应方法来完成工作。所以该类是一个对外接口的封装,APP可以直接通过SubscriptionManager来实现对相关service的调用。这种设计模式,android Framework 层用的很多,如SmsMnager、TelephonyManager等。

5、SubscriptionController

   实现为远程 service,在phone初始化时被创建,作为数据库的对外接口提供功能,其内部维护从数据库读取到的 subInfo list,并实现了很多getter和setter方法,如 getDisplayName / setDisplayName等,这些方法实现了subInfo信息的维护与查询等。

PS: SubscriptionManager 与 SubscriptionController 的交互方法
    // SubscriptionController init时 添加服务
protected void init(Context c) {
mContext = c;
mCM = CallManager.getInstance();
mTelephonyManager = TelephonyManager.from(mContext);

mAppOps = (AppOpsManager)mContext.getSystemService(Context.APP_OPS_SERVICE);

if(ServiceManager.getService("isub") == null) {
// 向 ServiceManager 添加服务,此处 isub 仅为一标记,供使用时匹配获取
ServiceManager.addService("isub", this);
}

if (DBG) logdl("[SubscriptionController] init by Context");
}

// SubscriptionManager 接口调用时获取服务
try {
ISub iSub = ISub.Stub.asInterface(ServiceManager.getService("isub"));
if (iSub != null) {
result = iSub.setDisplayNumber(number, subId);
}
} catch (RemoteException ex) {
// ignore it
}


四、subInfo 变化时的广播通知

subInfo信息被广泛使用到短信、电话、状态栏等息息相关的场景,所以其内部值的变化也被各类场景所关注,那么它是如何进行通知的呢?查看下相关的 setter 方法,以 setDataRoaming 方法为例
frameworks/opt/telephony/src/java/com/android/internal/telephony/SubscriptionController.java
    @Override
public int setDataRoaming(int roaming, int subId) {
if (DBG) logd("[setDataRoaming]+ roaming:" + roaming + " subId:" + subId);

enforceModifyPhoneState("setDataRoaming");

// Now that all security checks passes, perform the operation as ourselves.
final long identity = Binder.clearCallingIdentity();
try {
validateSubId(subId);
if (roaming < 0) {
if (DBG) logd("[setDataRoaming]- fail");
return -1;
}
ContentValues value = new ContentValues(1);
value.put(SubscriptionManager.DATA_ROAMING, roaming);
if (DBG) logd("[setDataRoaming]- roaming:" + roaming + " set");

int result = mContext.getContentResolver().update(SubscriptionManager.CONTENT_URI,
value, SubscriptionManager.UNIQUE_KEY_SUBSCRIPTION_ID + "=" +
Long.toString(subId), null);
// 通知 subInfo 信息变化
notifySubscriptionInfoChanged();

return result;
} finally {
Binder.restoreCallingIdentity(identity);
}
}


显然,这里 notifySubscriptionInfoChanged 方法完成了这一通知的任务,具体看下其实现
     public void notifySubscriptionInfoChanged() {
ITelephonyRegistry tr = ITelephonyRegistry.Stub.asInterface(ServiceManager.getService(
"telephony.registry"));
try {
if (DBG) logd("notifySubscriptionInfoChanged:");
tr.notifySubscriptionInfoChanged();
} catch (RemoteException ex) {
// Should never happen because its always available.
}

// FIXME: Remove if listener technique accepted.
broadcastSimInfoContentChanged();
}

/**
* Broadcast when SubscriptionInfo has changed
* FIXME: Hopefully removed if the API council accepts SubscriptionInfoListener
*/
private void broadcastSimInfoContentChanged() {
Intent intent = new Intent(TelephonyIntents.ACTION_SUBINFO_CONTENT_CHANGE);
mContext.sendBroadcast(intent);
intent = new Intent(TelephonyIntents.ACTION_SUBINFO_RECORD_UPDATED);
mContext.sendBroadcast(intent);
}


从上面的代码可以看到,subInfo 信息变化的通知分为了两类:一是 TelephonyRegistry.notifySubscriptionInfoChanged,二是直接发送广播通知。

至此,subInfo 更新与维护的流程,我们即可告一段落。