天天看點

Android9.0 sim卡讀取聯系人

轉載:

https://blog.csdn.net/firedancer0089/article/details/60762199

https://blog.csdn.net/mafei19870124/article/details/73521995

https://segmentfault.com/a/1190000011314014 (主要參考)

Android9.0 sim卡讀取聯系人

第一部分:讀取卡聯系人流程

讀取sim卡聯系人 分兩條路線,一條是用于資料的緩沖路線,一條是用于消息的傳遞

IccProvider.java (frameworks\opt\telephony\src\java\com\android\internal\telephony)

public class IccProvider extends ContentProvider {
           

從上面可以看到,真正的ICCProvider是在framework中繼承ContentProvider,處理ADN/FDN/SDN的query/insert/update/delete等操作,和SIM卡互動完成後,将資料改變資訊通知給ContentObserver,然後ContentObserver将資料變化的發送給注冊監聽的應用,Contacts應用做相應的同步動作。

UiccPhoneBookController.java

UiccPhoneBookController在ProxyController的構造方法中初始化。

mUiccPhoneBookController = new UiccPhoneBookController(mPhones);

UiccPhoneBookController的構造方法:

public class UiccPhoneBookController extends IIccPhoneBook.Stub {
    private static final String TAG = "UiccPhoneBookController";
    private Phone[] mPhone;
​
    /* only one UiccPhoneBookController exists */
    public UiccPhoneBookController(Phone[] phone) {
        if (ServiceManager.getService("simphonebook") == null) {
               ServiceManager.addService("simphonebook", this);
        }
        mPhone = phone;
    }
           

IccProvider通過AIDL調用UiccPhoneBookController:

IccProvider.java frameworks\opt\telephony\src\java\com\android\internal\telephony

private MatrixCursor loadFromEf(int efType, int subId) {
        if (DBG) log("loadFromEf: efType=0x" +
                Integer.toHexString(efType).toUpperCase() + ", subscription=" + subId);
​
        List<AdnRecord> adnRecords = null;
        try {
            IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
                    ServiceManager.getService("simphonebook"));
            if (iccIpb != null) {
                adnRecords = iccIpb.getAdnRecordsInEfForSubscriber(subId, efType);
            }
        } catch (RemoteException ex) {
            // ignore it
        } catch (SecurityException ex) {
            if (DBG) log(ex.toString());
        }
           

關于AIDL的實作後面詳細學習

IccPhoneBookInterfaceManager.java

IccPhoneBookInterfaceManager是在Phone的初始化時完成執行個體化:

protected void initOnce(CommandsInterface ci) {
    if (ci instanceof SimulatedRadioControl) {
        mSimulatedRadioControl = (SimulatedRadioControl) ci;
    }
​
    mCT = mTelephonyComponentFactory.makeGsmCdmaCallTracker(this);
    mIccPhoneBookIntManager = mTelephonyComponentFactory.makeIccPhoneBookInterfaceManager(this);
           

AdnRecordCache.java

AdnRecordCache在IccPhoneBookInterfaceManager構造方法中完成初始化:

public IccPhoneBookInterfaceManager(Phone phone) {
        this.mPhone = phone;
        IccRecords r = phone.getIccRecords();
        if (r != null) {
            mAdnCache = r.getAdnCache();
        }
    }
           

查詢SIM卡聯系人流程

1、 IccProvider.java

static塊中加入了adn的uri,adn/subid/#可以指定讀取的sim卡

static {
        URL_MATCHER.addURI("icc", "adn", ADN);
        URL_MATCHER.addURI("icc", "adn/subId/#", ADN_SUB);
        URL_MATCHER.addURI("icc", "fdn", FDN);
        URL_MATCHER.addURI("icc", "fdn/subId/#", FDN_SUB);
        URL_MATCHER.addURI("icc", "sdn", SDN);
        URL_MATCHER.addURI("icc", "sdn/subId/#", SDN_SUB);
    }
           

UiccPhoneBookController時一個Binder服務類,接口是IIccPhoneBook,服務名"simphonebook"

private MatrixCursor loadFromEf(int efType, int subId) {
        if (DBG) log("loadFromEf: efType=0x" +
                Integer.toHexString(efType).toUpperCase() + ", subscription=" + subId);
​
        List<AdnRecord> adnRecords = null;
        try {
            IIccPhoneBook iccIpb = IIccPhoneBook.Stub.asInterface(
                    ServiceManager.getService("simphonebook"));
            if (iccIpb != null) {
                adnRecords = iccIpb.getAdnRecordsInEfForSubscriber(subId, efType);
            }
        } catch (RemoteException ex) {
            // ignore it
        } catch (SecurityException ex) {
            if (DBG) log(ex.toString());
        }
​
        if (adnRecords != null) {
            // Load the results
            final int N = adnRecords.size();
            final MatrixCursor cursor = new MatrixCursor(ADDRESS_BOOK_COLUMN_NAMES, N);
            if (DBG) log("adnRecords.size=" + N);
            for (int i = 0; i < N ; i++) {
                loadRecord(adnRecords.get(i), cursor, i);
            }
            return cursor;
        } else {
            // No results to load
            Rlog.w(TAG, "Cannot load ADN records");
            return new MatrixCursor(ADDRESS_BOOK_COLUMN_NAMES);
        }
    }
           

2.UiccPhoneBookController

UiccPhoneBookController.java frameworks\opt\telephony\src\java\com\android\internal\telephony

getAdnRecordsInEfForSubscriber方法:

@Override
    public List<AdnRecord> getAdnRecordsInEfForSubscriber(int subId, int efid)
           throws android.os.RemoteException {
        //擷取執行個體,執行個體在文章開始Phone初始化時設定
        IccPhoneBookInterfaceManager iccPbkIntMgr =
                             getIccPhoneBookInterfaceManager(subId);
        if (iccPbkIntMgr != null) {
            //調用getAdnRecordsInEf方法
            return iccPbkIntMgr.getAdnRecordsInEf(efid);
        } else {
            Rlog.e(TAG,"getAdnRecordsInEf iccPbkIntMgr is" +
                      "null for Subscription:"+subId);
            return null;
        }
    }
           

3.IccPhoneBookInterfaceManager

// 在efid中加載AdnRecords并将它們作為mRecords傳回AdnRecords清單
    public List<AdnRecord> getAdnRecordsInEf(int efid) {
         //檢查權限
        if (mPhone.getContext().checkCallingOrSelfPermission(
                android.Manifest.permission.READ_CONTACTS)
                != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException(
                    "Requires android.permission.READ_CONTACTS permission");
        }
        //根據SIM卡類型設定EF_ID
        efid = updateEfForIccType(efid);
        if (DBG) logd("getAdnRecordsInEF: efid=0x" + Integer.toHexString(efid).toUpperCase());
        //線程同步
        synchronized(mLock) {
            checkThread();
            AtomicBoolean status = new AtomicBoolean(false);
            //回調message
            Message response = mBaseHandler.obtainMessage(EVENT_LOAD_DONE, status);
            if (mAdnCache != null) {
                //查詢聯系人,通過message傳回
                mAdnCache.requestLoadAllAdnLike(efid, mAdnCache.extensionEfForEf(efid), response);
                //此處線程wait(),mBaseHandler 擷取mRecords後notifyPending(),代碼才能繼續往下執行
                waitForResult(status);
            } else {
                loge("Failure while trying to load from SIM due to uninitialised adncache");
            }
        }
        return mRecords;
    }
           

4.AdnRecordCache查詢,requestLoadAllAdnLike()方法

/**
     * Responds with exception (in response) if efid is not a known ADN-like
     * record
     */
    public void requestLoadAllAdnLike (int efid, int extensionEf, Message response) {
        ArrayList<Message> waiters;
        ArrayList<AdnRecord> result;
​
        if (efid == EF_PBR) {
            result = mUsimPhoneBookManager.loadEfFilesFromUsim();
        } else {
            result = getRecordsIfLoaded(efid);//該方法實際是從緩存讀取資料
        }
​
        // Have we already loaded this efid?
        if (result != null) {
            if (response != null) {
                AsyncResult.forMessage(response).result = result;
                response.sendToTarget();
            }
​
            return;
        }
​
        // Have we already *started* loading this efid?
​
        waiters = mAdnLikeWaiters.get(efid);
​
        if (waiters != null) {//正在讀取中,把回調消息加入等待隊列中,return
            // There's a pending request for this EF already
            // just add ourselves to it
​
            waiters.add(response);
            return;
        }
​
        // Start loading efid
​
        waiters = new ArrayList<Message>();
        waiters.add(response);
​
        mAdnLikeWaiters.put(efid, waiters);
​
​
        if (extensionEf < 0) {
            // respond with error if not known ADN-like record
​
            if (response != null) {
                AsyncResult.forMessage(response).exception
                    = new RuntimeException("EF is not known ADN-like EF:0x" +
                        Integer.toHexString(efid).toUpperCase());
                response.sendToTarget();
            }
​
            return;
        }
​
        new AdnRecordLoader(mFh).loadAllFromEF(efid, extensionEf,
            obtainMessage(EVENT_LOAD_ALL_ADN_LIKE_DONE, efid, 0));//正真讀取
    }
           

流程分析已經寫在注釋中,usim是另一條分支(本流程不做解析),

第一條線資料的緩沖,繼續看loadAllFromEF

frameworks/opt/telephony/src/java/com/android/internal/telephony/uicc/AdnRecordLoader.java

/**
     * Resulting ArrayList&lt;adnRecord> is placed in response.obj.result
     * or response.obj.exception is set
     */
    public void loadAllFromEF(int ef, int extensionEF,
                Message response) {
        mEf = ef;
        mExtensionEF = extensionEF;
        mUserResponse = response;
​
        /* If we are loading from EF_ADN, specifically
         * specify the path as well, since, on some cards,
         * the fileid is not unique.
         */
        mFh.loadEFLinearFixedAll(
                ef, getEFPath(ef),
                obtainMessage(EVENT_ADN_LOAD_ALL_DONE));
    }
           

IccFileHandler 調用loadEFLinearFixedAll發送請求,讀取結果會在handleMessage中處理

@Override
    public void handleMessage(Message msg) {
    ......
                    case EVENT_ADN_LOAD_ALL_DONE:
                    ar = (AsyncResult)(msg.obj);
                    ArrayList<byte[]> datas = (ArrayList<byte[]>)(ar.result);
​
                    if (ar.exception != null) {
                        throw new RuntimeException("load failed", ar.exception);
                    }
​
                    mAdns = new ArrayList<AdnRecord>(datas.size());
                    mResult = mAdns;
                    mPendingExtLoads = 0;
​
                    for(int i = 0, s = datas.size() ; i < s ; i++) {
                        adn = new AdnRecord(mEf, 1 + i, datas.get(i));
                        mAdns.add(adn);
​
                        if (adn.hasExtendedRecord()) {
                            // If we have a valid value in the ext record field,
                            // we're not done yet: we need to read the corresponding
                            // ext record and append it
​
                            mPendingExtLoads++;
​
                            mFh.loadEFLinearFixed(
                                mExtensionEF, adn.mExtRecord,
                                obtainMessage(EVENT_EXT_RECORD_LOAD_DONE, adn));
                        }
                    }
           

mAdns.add(adn); for循環中向mAdns添加資料。

擷取到了卡聯系人總數目,先用空值初始化mAdn清單,然後發送消息EVENT_EXT_RECORD_LOAD_DONE

通過handleMessage中讀取資料

case EVENT_EXT_RECORD_LOAD_DONE:
                    ar = (AsyncResult)(msg.obj);
                    data = (byte[])(ar.result);
                    adn = (AdnRecord)(ar.userObj);
​
                    if (ar.exception == null) {
                        Rlog.d(LOG_TAG,"ADN extension EF: 0x"
                                + Integer.toHexString(mExtensionEF)
                                + ":" + adn.mExtRecord
                                + "\n" + IccUtils.bytesToHexString(data));
​
                        adn.appendExtRecord(data);
                    }
                    else {
                        // If we can't get the rest of the number from EF_EXT1, rather than
                        // providing the partial number, we clear the number since it's not
                        // dialable anyway. Do not throw exception here otherwise the rest
                        // of the good records will be dropped.
​
                        Rlog.e(LOG_TAG, "Failed to read ext record. Clear the number now.");
                        adn.setNumber("");
                    }
​
                    mPendingExtLoads--;
                    // result should have been set in
                    // EVENT_ADN_LOAD_DONE or EVENT_ADN_LOAD_ALL_DONE
                break;
           

第二條線路消息傳遞:AdnRecordCache通過requestLoadAllAdnLike()調用AdnRecordLoader.loadAllFromEF()發消息,EVENT_LOAD_ALL_ADN_LIKE_DONE消息處理:

public void requestLoadAllAdnLike (int efid, int extensionEf, Message response) {
        ......
        new AdnRecordLoader(mFh).loadAllFromEF(efid, extensionEf,
        obtainMessage(EVENT_LOAD_ALL_ADN_LIKE_DONE, efid, 0));
           

消息處理:

case EVENT_LOAD_ALL_ADN_LIKE_DONE:
                /* arg1 is efid, obj.result is ArrayList<AdnRecord>*/
                ar = (AsyncResult) msg.obj;
                efid = msg.arg1;
                ArrayList<Message> waiters;
​
                waiters = mAdnLikeWaiters.get(efid);
                mAdnLikeWaiters.delete(efid);
​
                if (ar.exception == null) {
                    mAdnLikeFiles.put(efid, (ArrayList<AdnRecord>) ar.result);
                }
                notifyWaiters(waiters, ar);
                break;
           

一路向上傳遞消息,這裡的ar其實就包含了聯系人資料清單ArrayList

回到IccPhoneBookInterfaceManager.java

case EVENT_LOAD_DONE://查詢完成
                ar = (AsyncResult)msg.obj;
                synchronized (mLock) {
                    if (ar.exception == null) {
                        if(DBG) logd("Load ADN records done");
                        //傳回adn list
                        mRecords = (List<AdnRecord>) ar.result;
                    } else {
                        if(DBG) logd("Cannot load ADN records");
                        mRecords = null;
                    }
                    //notify()喚醒
                    notifyPending(ar);
                }
                break;
           

整個流程走完了。可以看出名稱叫做IccProvider,其實沒有建立任何資料庫。第一次的查詢是通過發送ril請求讀取sim卡得到資料,後續用緩存傳回資料。

第二部分:Contacts讀取Sim卡聯系人的流程

Android9.0 sim卡讀取聯系人

packages/apps/Contacts/src/com/mediatek/contacts/simcontact/BootCmpReceiver.java

public void onReceive(Context context, Intent intent) {
        ...
        if (action.equals(TelephonyIntents.ACTION_PHB_STATE_CHANGED)) {
                    processPhoneBookChanged(context, intent);
                }
        ...
    }
           

收到TelephonyIntents.ACTION_PHB_STATE_CHANGED廣播後,該廣播表示卡聯系人可用不可用,調用processPhoneBookChanged

private void processPhoneBookChanged(Context context, Intent intent) {
            ...
            if (phbReady && subId > 0) {
                startSimService(context, subId, SIMServiceUtils.SERVICE_WORK_IMPORT);
            } else if (subId > 0 && !phbReady) {
                startSimService(context, subId, SIMServiceUtils.SERVICE_WORK_REMOVE);
            }
        }
           

廣播處理有兩個分支,一個是删除卡聯系人,一個是導入卡聯系人

packages/apps/Contacts/src/com/mediatek/contacts/simservice/SIMProcessorService.java

@Override
        public void onCreate() {
            super.onCreate();
            Log.i(TAG, "[onCreate]...");
            mProcessorManager = new SIMProcessorManager(this, mListener);
        }
     
        @Override
        public void onStart(Intent intent, int startId) {
            super.onStart(intent, startId);
            processIntent(intent);
        }

     private void processIntent(Intent intent) {
            ...
            mProcessorManager.handleProcessor(getApplicationContext(), subId, workType, intent);
        }
           

一路調用到handleProcessor,注意mProcessorManager初始化的時候傳入了接口的實作,這樣mProcessorManager就可以通知SIMProcessorService工作開始或者完畢

private SIMProcessorManager.ProcessorManagerListener mListener

packages/apps/Contacts/src/com/mediatek/contacts/simservice/SIMProcessorManager.java

public void handleProcessor(Context context, int subId, int workType, Intent intent) {
            Log.i(TAG, "[handleProcessor] subId=" + subId + ",time=" + System.currentTimeMillis());
            SIMProcessorBase processor = createProcessor(context, subId, workType, intent);
            if (processor != null && mListener != null) {
                Log.d(TAG, "[handleProcessor]Add processor [subId=" + subId + "] to threadPool.");
                mListener.addProcessor(/* 1000 + slotId * 300 */0, processor);
            }
        }

        private SIMProcessorBase createProcessor(Context context, int subId, int workType,
                Intent intent, ProcessorCompleteListener listener) {
            ...
            if (workType == SIMServiceUtils.SERVICE_WORK_IMPORT) {
                processor = new SIMImportProcessor(context, subId, intent, listener);
            ...
        }
           

createProcessor生成了processor,然後調用mListener的方法,這個就是SIMProcessorService中實作的,addProcessor開始導入聯系人的工作:

@Override
            public void addProcessor(long scheduleTime, ProcessorBase processor) {
                if (processor != null) {
                    try {
                        mExecutorService.execute(processor);
                    } catch (RejectedExecutionException e) {
                        Log.e(TAG, "[addProcessor] RejectedExecutionException: " + e.toString());
                    }
                }
            }
           

processor是繼承自ProcessorBase。

packages/apps/ContactsCommon/src/com/android/contacts/common/vcard/ProcessorBase.java

public abstract class ProcessorBase implements RunnableFuture {

ProcessorBase實作了RunnableFuture,是以它可以放到線程池區運作。

packages/apps/Contacts/src/com/mediatek/contacts/simservice/SIMProcessorBase.java

public void run() {
            try {
                doWork();
            } finally {
                mDone = true;
                if (mListener != null && !mCanceled) {
                    mListener.onProcessorCompleted(mIntent);
                }
            }
        }
           

線程池是調用run方法開啟工作的,run函數中調用doWork完成工作,用mListener接口通知SIMProcessorManager工作完畢

packages/apps/Contacts/src/com/mediatek/contacts/simservice/SIMImportProcessor.java

@Override
        public void doWork() {
            ...
            SIMServiceUtils.deleteSimContact(mContext, mSubId);
            ...
     
            int simType = SimCardUtils.getSimTypeBySubId(mSubId);
            final Uri iccUri = SubInfoUtils.getIccProviderUri(mSubId);
            Cursor simCursor = querySimContact(mContext, mSubId, simType, iccUri);
            Log.i(TAG, "[dowork]simType = " + simType + ",simType =" + simType + ",mSubId = " + mSubId);
            importAllSimContacts(mContext, mSubId, simCursor, simType);
            if (simCursor != null) {
                simCursor.close();
            }
        }
           

首先删除所有資料庫中的卡聯系人,然後查詢卡聯系人,擷取卡聯系人資料後導入到ContactsProvider中。

private Cursor querySimContact(Context context, int subId, int simType, Uri iccUri) {
            ...
            cursor = context.getContentResolver().query(iccUri, COLUMN_NAMES, null, null, null);
            ...
            return cursor;
        }
           

通過IccProvider查詢卡聯系人

private void importAllSimContacts(Context context, final Cursor cursor,
                final ContentResolver resolver, int subId, int simType, HashSet<Long> insertSimIdSet,
                boolean importSdnContacts) {
           ...
           while (cursor.moveToNext()) {
                    ...
                    i = actuallyImportOneSimContact(context, cursor, resolver, subId, simType,
                            indexInSim, importSdnContacts, operationList, i, account, isUsim,
                            accountSubId, countryCode);
                    ...
                    if (i > MAX_OP_COUNT_IN_ONE_BATCH) {
                        ...
                        resolver.applyBatch(ContactsContract.AUTHORITY, operationList);
                        ...
                    }
           }
           ...
        }
           

基本流程是依據cursor利用actuallyImportOneSimContact生成資料庫插入的operationlist,然後在每大于90個operation就批量操作一次,循環上訴流程直到處理完畢。

doWork結束後會回調接口ProcessorCompleteListener,然後關閉線程池和關閉service。