Android原始碼----操作SIM卡對聯絡人的影響(一)
前言
SIM(Subscriber Identification Module )卡:也稱為使用者身份識別卡、智慧卡,GSM數字行動電話機必須裝上此卡方能使用。SIM卡有很多功能,其中有一項是儲存使用者相關資料,這些資料主要分為五類:第一類是固定存放的資料,這類資料在ME(Mobile Equipment)被出售之前由SIM卡中心寫入,包括國際移動使用者識別號(IMSI)、鑑權金鑰(KI)等;第二類是暫時存放的有關網路的資料,如位置區域識別碼(LAI)、移動使用者暫時識別碼(TMSI)、禁止接入的公共電話網程式碼等;第三類是相關的業務程式碼,如個人識別碼(PIN)、解鎖碼(PUK)、計費費率等;第四類是電話號碼薄,是使用者隨時存入的電話號碼。第五類是簡訊息。本文我們主要討論SIM卡中儲存電話號碼簿的情況,一般的SIM卡的IC晶片中,有128kb的儲存容量,可供儲存500組電話號碼及其對應的姓名文字,可以在原始碼中定義(MAX_OPERATIONS_SIZE)。
由於SIM卡的儲存量有限,所儲存的聯絡人欄位會有一定的限制,在AOSP(Android Open-Source Project)原生中,可儲存的欄位有:“name”、“number”、“emails”、"_id"分別表示單個聯絡人的姓名、電話號碼、郵箱號碼、id;在高通平臺中,可儲存的多了一個欄位:“name”、“number”、“emails”、“anrs”、"_id"其中"anrs"表示單個聯絡人的第二個電話號碼。
操作SIM卡中的聯絡人主要分為三種情況:熱插拔SIM卡、在Contacts的Setting中Import/Export SIM卡中的聯絡人、開機自動匯入SIM卡中聯絡人資訊。
本文涉及的原始碼解析是基於android8平臺,且僅針對一張SIM卡的操作進行分析
熱插拔SIM卡
每次熱插拔SIM卡,會將資料庫中關於SIM卡的聯絡人進行增刪,SimStateReceiver通過接收RIL層上報的SIM卡狀態改變的廣播判斷當前SIM卡的狀態,並啟動SimContactsService進行對應的增刪操作,值得注意的一點是在AOSP中並沒有SimStateReceiver和SimContactsService等相關程式碼,需要依賴高通或MTK提供的相關patch。以高通專案為例,路徑大致為(vendor/qcom/proprietary/telephony-apps/SimContacts)。
拔SIM流程圖如下:
在熱拔SIM卡時,會收到關於SIM狀態改變的廣播(android.intent.action.SIM_STATE_CHANGED),此時SIM卡的狀態為ABSENT。在SimStateReceiver收到廣播之後,對SIM卡的狀態進行設定:
public void onReceive(Context context, Intent intent) { final String action = intent.getAction(); mContext = context; if (TelephonyIntents.ACTION_SIM_STATE_CHANGED.equals(action)) { final int slotId = intent.getIntExtra(PhoneConstants.SLOT_KEY, SubscriptionManager.getPhoneId(SubscriptionManager.getDefaultSubscriptionId())); final String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE); final int simState; ...... else if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra) || IccCardConstants.INTENT_VALUE_ICC_UNKNOWN.equals(stateExtra) || IccCardConstants.INTENT_VALUE_ICC_CARD_IO_ERROR.equals(stateExtra)) { simState = SimContactsConstants.SIM_STATE_ERROR; } sendSimState(slotId, simState); } ...... }
設定SIM卡的狀態為 SIM_STATE_ERROR,接著呼叫sendSimState()方法。
private void sendSimState(int slotId, int state) {
Bundle args = new Bundle();
args.putInt(PhoneConstants.SLOT_KEY, slotId);
args.putInt(SimContactsService.OPERATION, SimContactsService.MSG_SIM_STATE_CHANGED);
args.putInt(SimContactsService.SIM_STATE, state);
mContext.startService(new Intent(mContext, SimContactsService.class)
.putExtras(args));
}
設定好Bundle直接啟動服務SimContactsService,在服務中獲取Bundle的資訊,交給handle來處理,MSG.what=MSG_SIM_STATE_CHANGED:
mServiceHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Bundle args = (Bundle) msg.obj;
switch (msg.what) {
......
case MSG_SIM_STATE_CHANGED:
final int state = args.getInt(SimContactsService.SIM_STATE);
int slotId = args.getInt(PhoneConstants.SLOT_KEY,
SubscriptionManager.getPhoneId(SubscriptionManager.
getDefaultVoicePhoneId()));
Log.d(TAG, "on sim state changed event, state:" +
state + ", slot:" + slotId);
if (state == mSimState[slotId] && mInvalidSubInfo[slotId] == false) {
break;
}
mSimState[slotId] = state;
if (mSimState[slotId] == SimContactsConstants.SIM_STATE_NOT_READY
|| mSimState[slotId] == SimContactsConstants.SIM_STATE_ERROR) {
// To handle card absent/error, hot swap related cases.
deleteDatabaseSimContacts(slotId);
deleteSimAccount(slotId);
break;
}
......
}
}
}
接下來只做了兩件事情:刪除資料庫中存於SIM卡的聯絡人;清除SIMAccount。
private void deleteDatabaseSimContacts(int slotId) {
if (!hasContactsProviderExist()) {
Log.d(TAG, "contacts content provider not exist");
return;
}
getContentResolver().delete(ContactsContract.RawContacts.CONTENT_URI,
SIM_DATABASE_SELECTION, getSimAccountDBSelectArgs(slotId));
Log.d(TAG, "deleteDatabaseSimContacts");
}
private void deleteSimAccount(int slotId) {
if (findAccount(SimContactsConstants.getSimAccountName(slotId),
SimContactsConstants.ACCOUNT_TYPE_SIM) != null) {
accountManager.removeAccount(
new Account(SimContactsConstants.getSimAccountName(slotId),
SimContactsConstants.ACCOUNT_TYPE_SIM), null, null);
}
}
插SIM卡流程如下
在啟動SimContactsService服務之前的流程與拔SIM卡相同,唯一的區別是此時SIM的狀態為READY,依然收到SIM狀態改變的廣播,在handle的處理過程中,由於 MSG_SIM_STATE_CHANGED分支中未有滿足break的條件,因此順延處理下一條case:
case MSG_SIM_REFRESH:
Log.d(TAG, "on sim refresh event");
int sub = args.getInt(PhoneConstants.SLOT_KEY,
SubscriptionManager.getPhoneId(SubscriptionManager.getDefaultSubscriptionId()));
if (mSimState[sub] == SimContactsConstants.SIM_STATE_READY) {
if (!isSimOperationInprocess[sub]) {
handleSimOp(sub);
} else {
Log.d(TAG, "queue refresh sim op");
refreshQueue.put(sub, MSG_SIM_REFRESH);
}
} else if (mSimState[sub] == SimContactsConstants.SIM_STATE_ERROR) {
handleNoSim(sub);
}
break;
}
接著進入handleSimOp方法,主要也是做兩件事情:刪除資料庫中關於SIM卡的聯絡人;查詢當前SIM卡中的聯絡人並存入資料庫。
private void querySimContacts(int slotId) {
Uri uri = null;
if (!isMultiSimEnabled()) {
uri = Uri.parse("content://icc/adn"); //單卡URI
} else {
SubscriptionInfo subInfo = SubscriptionManager.from(mContext)
.getActiveSubscriptionInfoForSimSlotIndex(slotId);
if (subInfo != null) {
mInvalidSubInfo[slotId] = false;
uri = Uri.parse("content://icc/adn/subId/" + subInfo.getSubscriptionId()); //多卡URI
} else {
mInvalidSubInfo[slotId] = true;
return;
}
}
mQuerySimHandler[slotId].startQuery(QUERY_TOKEN, null, uri, null, null, null, null);
}
其中mQuerySimHandler繼承了AsyncQueryHandler進行非同步查詢,將查詢的cursor存放在mSimCursor中,開啟新執行緒,先為SIM建立Account(createSimAccountIfNotExist()),並將所有的SIM卡聯絡人新增到資料庫中。
private static void actuallyImportOneSimContact(
final Cursor cursor, final ContentResolver resolver, Account account,
ArrayList<ContentProviderOperation> operationList) {
final String name = cursor.getString(cursor.getColumnIndex(COLUMN_NAME_NAME));//獲取單個聯絡人的姓名
final String phoneNumber = cursor.getString(cursor.getColumnIndex(COLUMN_NAME_NUMBER));//獲取單個聯絡人的電話號碼
String emailAddresses = null;
int ref = operationList.size();
try {
emailAddresses = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NAME_EMAIL));//獲取單個聯絡人的郵箱地址
} catch (IllegalArgumentException e) {
}
String anrs = null;
try {
anrs = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_NAME_ANR));//獲取單個聯絡人的another電話號碼
} catch (IllegalArgumentException e) {
}
if (DBG)
Log.d(TAG,String.format("name: %s, number: %s, anrs: %s, emails: %s", name, phoneNumber,
anrs, emailAddresses));
final String[] emailAddressArray;
final String[] anrArray;
if (!TextUtils.isEmpty(emailAddresses)) {
emailAddressArray = emailAddresses.split(",");
} else {
emailAddressArray = null;
}
if (!TextUtils.isEmpty(anrs)) {
anrArray = anrs.split(":");
} else {
anrArray = null;
}
ContentProviderOperation.Builder builder =
ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);//建立INSERT事務
builder.withValue(RawContacts.AGGREGATION_MODE, RawContacts.AGGREGATION_MODE_DISABLED);
if (account != null) {
builder.withValue(RawContacts.ACCOUNT_NAME, account.name);//將此時插入的聯絡人Account設為SIM
builder.withValue(RawContacts.ACCOUNT_TYPE, account.type);
}
operationList.add(builder.build());
if (!TextUtils.isEmpty(name)) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, ref);
builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
builder.withValue(StructuredName.GIVEN_NAME, name);
operationList.add(builder.build());//將姓名相關引數新增到operationList中
}
if (!TextUtils.isEmpty(phoneNumber)) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Phone.RAW_CONTACT_ID, ref);
builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
builder.withValue(Phone.TYPE, Phone.TYPE_MOBILE);
builder.withValue(Phone.NUMBER, phoneNumber);
builder.withValue(Data.IS_PRIMARY, 1);
operationList.add(builder.build());//將聯絡方式相關引數新增到operationList中
}
if (anrArray != null) {
for (String anr : anrArray) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Phone.RAW_CONTACT_ID, ref);
builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
builder.withValue(Phone.TYPE, Phone.TYPE_HOME);
builder.withValue(Phone.NUMBER, anr);
operationList.add(builder.build());//將another聯絡方式相關引數新增到operationList中
}
}
if (emailAddressArray != null) {
for (String emailAddress : emailAddressArray) {
builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
builder.withValueBackReference(Email.RAW_CONTACT_ID, ref);
builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
builder.withValue(Email.TYPE, Email.TYPE_MOBILE);
builder.withValue(Email.ADDRESS, emailAddress);
operationList.add(builder.build());//將Email相關引數新增到operationList中
}
}
}
將從SIM卡中查詢到的聯絡人的相關內容通過上述方法新增到ArrayList< ContentProviderOperation> operationList列表中,呼叫ContentResolver.applyBatch()將建立的Insert方法進行事務處理。線上程中處理完上述內容之後,需要做好收尾工作,關閉cursor等操作,防止記憶體洩露的問題。
finally {
if (mSimCursor[mSlotId] != null
&& !mSimCursor[mSlotId].isClosed()) {
mSimCursor[mSlotId].close();
mSimCursor[mSlotId] = null;
}
isSimOperationInprocess[mSlotId] = false;
sendPendingSimRefreshUpdateMsg(mSlotId);
}
到此完成了插入SIM時聯絡人匯入到資料庫的整個過程。關於頁面的重新整理,涉及到Loader的內容,之後再進行分析羅列。