1. 程式人生 > >Android原始碼----操作SIM卡對聯絡人的影響(一)

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的內容,之後再進行分析羅列。