Android6.0來(lái)電號(hào)碼與電話薄聯(lián)系人進(jìn)行匹配
本文將介紹系統(tǒng)接收到來(lái)電之后,如何在電話薄中進(jìn)行匹配聯(lián)系人的流程。分析將從另外一篇文章(基于Android6.0的RIL框架層模塊分析)中提到的與本文內(nèi)容相關(guān)的代碼開(kāi)始。
//packages/service/***/Call.java public void handleCreateConnectionSuccess( CallIdMapper idMapper, ParcelableConnection connection) { setHandle(connection.getHandle(), connection.getHandlePresentation());//這個(gè)函數(shù)很重要,會(huì)啟動(dòng)一個(gè)查詢 setCallerDisplayName(connection.getCallerDisplayName(), connection.getCallerDisplayNamePresentation()); setExtras(connection.getExtras()); if (mIsIncoming) { // We do not handle incoming calls immediately when they are verified by the connection // service. We allow the caller-info-query code to execute first so that we can read the // direct-to-voicemail property before deciding if we want to show the incoming call to // the user or if we want to reject the call. mDirectToVoicemailQueryPending = true; // Timeout the direct-to-voicemail lookup execution so that we dont wait too long before // showing the user the incoming call screen. mHandler.postDelayed(mDirectToVoicemailRunnable, Timeouts.getDirectToVoicemailMillis( mContext.getContentResolver())); } }
這個(gè)setHandle函數(shù)如下:
//Call.java public void setHandle(Uri handle, int presentation) { startCallerInfoLookup(); } private void startCallerInfoLookup() { final String number = mHandle == null ? null : mHandle.getSchemeSpecificPart(); mQueryToken++; // Updated so that previous queries can no longer set the information. mCallerInfo = null; if (!TextUtils.isEmpty(number)) { mHandler.post(new Runnable() { @Override public void run() { mCallerInfoAsyncQueryFactory.startQuery(mQueryToken, mContext,number,mCallerInfoQueryListener,Call.this); }}); } }
注意后面post的那個(gè)Runnable。這個(gè)就是啟動(dòng)查詢號(hào)碼的邏輯了。這個(gè)mCallerInfoAsyncQueryFactory的賦值的流程比較曲折。在TelecomService被連接上調(diào)用onBind的時(shí)候,會(huì)調(diào)用initializeTelecomSystem函數(shù)。那這個(gè)TelecomService是在哪里被啟動(dòng)的呢?在TelecomLoaderService.java里面定義了:
private static final ComponentName SERVICE_COMPONENT = new ComponentName( "com.android.server.telecom", "com.android.server.telecom.components.TelecomService"); private void connectToTelecom() { synchronized (mLock) { TelecomServiceConnection serviceConnection = new TelecomServiceConnection(); Intent intent = new Intent(SERVICE_ACTION); intent.setComponent(SERVICE_COMPONENT); // Bind to Telecom and register the service if (mContext.bindServiceAsUser(intent, serviceConnection, flags, UserHandle.OWNER)) { mServiceConnection = serviceConnection; } }} public void onBootPhase(int phase) {//這個(gè)在系統(tǒng)啟動(dòng)階段就會(huì)觸發(fā) if (phase == PHASE_ACTIVITY_MANAGER_READY) { connectToTelecom(); }}
所以從這里看,在系統(tǒng)啟動(dòng)階段就會(huì)觸發(fā)TelecomService這個(gè)service,且在成功連接到服務(wù)之后,將調(diào)用ServiceManager.addService(Context.TELECOM_SERVICE, service),將這個(gè)服務(wù)添加到系統(tǒng)服務(wù)中了。這個(gè)類的構(gòu)造函數(shù)中,在調(diào)用函數(shù)initializeTelecomSystem初始化TelecomSystem時(shí),就實(shí)例化了一個(gè)內(nèi)部匿名對(duì)象,并且在TelecomSystem的構(gòu)造函數(shù)中初始化一個(gè)mCallsManager時(shí)將該匿名對(duì)象傳入,而在CallsManager的processIncomingCallIntent中會(huì)用這個(gè)函數(shù)初始化一個(gè)Call對(duì)象。所以這個(gè)mCallerInfoAsyncQueryFactory的實(shí)際內(nèi)容見(jiàn)TelecomService中的initializeTelecomSystem:
//TelecomService.java TelecomSystem.setInstance( new TelecomSystem( context, new MissedCallNotifierImpl(context.getApplicationContext()), new CallerInfoAsyncQueryFactory() { @Override public CallerInfoAsyncQuery startQuery(int token, Context context, String number,CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) { return CallerInfoAsyncQuery.startQuery(token, context, number, listener, cookie); }}, new HeadsetMediaButtonFactory() {}, new ProximitySensorManagerFactory() {}, new InCallWakeLockControllerFactory() {}, new ViceNotifier() {}));
可以看到,通過(guò)startQuery來(lái)查詢傳入的number的動(dòng)作。我們來(lái)看看CallerInfoAsyncQuery的startQuery函數(shù)。
//frameworks/base/telephony/java/com/android/internal/CallerInfoAsyncQuery.java /** * Factory method to start the query based on a number. * * Note: if the number contains an "@" character we treat it * as a SIP address, and look it up directly in the Data table * rather than using the PhoneLookup table. * TODO: But eventually we should expose two separate methods, one for * numbers and one for SIP addresses, and then have * PhoneUtils.startGetCallerInfo() decide which one to call based on * the phone type of the incoming connection. */ public static CallerInfoAsyncQuery startQuery(int token, Context context, String number, OnQueryCompleteListener listener, Object cookie) { int subId = SubscriptionManager.getDefaultSubId(); return startQuery(token, context, number, listener, cookie, subId); } /** * Factory method to start the query with a Uri query spec. */ public static CallerInfoAsyncQuery startQuery(int token, Context context, Uri contactRef, OnQueryCompleteListener listener, Object cookie) { c.mHandler.startQuery(token, cw, // cookie contactRef, // uri,注意這里的查詢地址 null, // projection null, // selection null, // selectionArgs null); // orderBy return c; }
注意看注釋,該函數(shù)還會(huì)對(duì)SIP號(hào)碼(包含@的號(hào)碼)進(jìn)行處理,還有緊急號(hào)碼和語(yǔ)音郵箱號(hào)碼進(jìn)行區(qū)分。實(shí)際上,當(dāng)對(duì)一個(gè)號(hào)碼進(jìn)行查詢的時(shí)候,這三個(gè)startQuery都用到了。注意,上面的startQuery會(huì)根據(jù)結(jié)果對(duì)connection的值進(jìn)行修改。
其中將號(hào)碼轉(zhuǎn)換成uri格式的數(shù)據(jù),后續(xù)會(huì)對(duì)這個(gè)數(shù)據(jù)進(jìn)行查詢:
//frameworks/base/***/CallerInfoAsyncQuery.java public static CallerInfoAsyncQuery startQuery(int token, Context context, String number, OnQueryCompleteListener listener, Object cookie, int subId) { // Construct the URI object and query params, and start the query. final Uri contactRef = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.buildUpon().appendPath(number) .appendQueryParameter(PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS, String.valueOf(PhoneNumberUtils.isUriNumber(number))) .build(); CallerInfoAsyncQuery c = new CallerInfoAsyncQuery(); c.allocate(context, contactRef); //create cookieWrapper, start query CookieWrapper cw = new CookieWrapper(); cw.listener = listener; cw.cookie = cookie; cw.number = number; cw.subId = subId; // check to see if these are recognized numbers, and use shortcuts if we can. if (PhoneNumberUtils.isLocalEmergencyNumber(context, number)) { cw.event = EVENT_EMERGENCY_NUMBER; } else if (PhoneNumberUtils.isVoiceMailNumber(subId, number)) { cw.event = EVENT_VOICEMAIL_NUMBER; } else { cw.event = EVENT_NEW_QUERY; } c.mHandler.startQuery(token, cw, // cookie contactRef, // uri null, // projection null, // selection null, // selectionArgs null); // orderBy return c; }
這個(gè)函數(shù)里面的contactRef的值應(yīng)該是“content://com.android.contacts/phone_lookup_enterprise/13678909678/sip?”類似的。
實(shí)際上這個(gè)query是調(diào)用CallerInfoAsyncQueryHandler的startQuery函數(shù),而這個(gè)函數(shù)是直接調(diào)用它的父類AsyncQueryHandler的同名函數(shù)。
//AsyncQueryHandler.java public void startQuery(int token, Object cookie, Uri uri, String[] projection, String selection, String[] selectionArgs, String orderBy) { // Use the token as what so cancelOperations works properly Message msg = mWorkerThreadHandler.obtainMessage(token); msg.arg1 = EVENT_ARG_QUERY; WorkerArgs args = new WorkerArgs(); args.handler = this; args.uri = uri; msg.obj = args; mWorkerThreadHandler.sendMessage(msg); }
這個(gè)mWorkerThreadHandler是在CallerInfoAsyncQueryHandler函數(shù)覆寫父類的createHandler函數(shù)中賦值,是CallerInfoWorkerHandler類型。所以后續(xù)的處理函數(shù)是該類的handleMessage函數(shù)。
//AsyncQueryHandler.java public void handleMessage(Message msg) { WorkerArgs args = (WorkerArgs) msg.obj; CookieWrapper cw = (CookieWrapper) args.cookie; if (cw == null) { // Normally, this should never be the case for calls originating // from within this code. // However, if there is any code that this Handler calls (such as in // super.handleMessage) that DOES place unexpected messages on the // queue, then we need pass these messages on. } else { switch (cw.event) { case EVENT_NEW_QUERY://它的值跟AsyncQueryHandler的EVENT_ARG_QUERY一樣,都是1 //start the sql command. super.handleMessage(msg); break; case EVENT_END_OF_QUEUE: // query was already completed, so just send the reply. // passing the original token value back to the caller // on top of the event values in arg1. Message reply = args.handler.obtainMessage(msg.what); reply.obj = args; reply.arg1 = msg.arg1; reply.sendToTarget(); break; default: }}}}
這個(gè)super就是AsyncQueryHandler的內(nèi)部類WorkerHandler了。
//AsyncQueryHandler.java protected class WorkerHandler extends Handler { @Override public void handleMessage(Message msg) { final ContentResolver resolver = mResolver.get(); WorkerArgs args = (WorkerArgs) msg.obj; int token = msg.what; int event = msg.arg1; switch (event) { case EVENT_ARG_QUERY: Cursor cursor; try { cursor = resolver.query(args.uri, args.projection, args.selection, args.selectionArgs, args.orderBy); // Calling getCount() causes the cursor window to be filled, // which will make the first access on the main thread a lot faster. if (cursor != null) { cursor.getCount(); }} args.result = cursor; break; } // passing the original token value back to the caller // on top of the event values in arg1. Message reply = args.handler.obtainMessage(token); reply.obj = args; reply.arg1 = msg.arg1; reply.sendToTarget(); }}
可以看到流程就是簡(jiǎn)單的用resolver.query來(lái)查詢指定的query URI,然后將返回值通過(guò)消息機(jī)制發(fā)送到AsyncQueryHandler的handleMessage里面處理,而在這里會(huì)調(diào)用CallerInfoAsyncQuery的onQueryComplete函數(shù)。注意這個(gè)ContentResolver是在uri上查詢結(jié)果,而這個(gè)uri是由某個(gè)ContentProvider來(lái)提供的。注意這個(gè)地址里面的authorities里面的值為”com.android.contacts”,同樣看看ContactsProvider的androidmanifest.xml文件:
<provider android:name="ContactsProvider2" android:authorities="contacts;com.android.contacts" android:readPermission="android.permission.READ_CONTACTS" android:writePermission="android.permission.WRITE_CONTACTS"> <path-permission android:pathPrefix="/search_suggest_query" android:readPermission="android.permission.GLOBAL_SEARCH" /> <path-permission android:pathPattern="/contacts/.*/photo" android:readPermission="android.permission.GLOBAL_SEARCH" /> <grant-uri-permission android:pathPattern=".*" /> </provider>
所以最后這個(gè)查詢是由ContactsProvider來(lái)執(zhí)行的。
我們來(lái)看看查詢完成之后,調(diào)用CallerInfoAsyncQuery的onQueryComplete函數(shù)的具體流程:
protected void onQueryComplete(int token, Object cookie, Cursor cursor) { // check the token and if needed, create the callerinfo object. if (mCallerInfo == null) { if (cw.event == EVENT_EMERGENCY_NUMBER) { } else if (cw.event == EVENT_VOICEMAIL_NUMBER) { } else { mCallerInfo = CallerInfo.getCallerInfo(mContext, mQueryUri, cursor); } } } //notify the listener that the query is complete. if (cw.listener != null) { cw.listener.onQueryComplete(token, cw.cookie, mCallerInfo); } } }
注意,上面代碼里面的CallerInfo.getCallerInfo非常重要。在這里面會(huì)使用查詢處理的cursor結(jié)果,并將合適的結(jié)果填充到mCallerInfo,將其傳遞到cw.listener.onQueryComplete函數(shù)中,作為最終結(jié)果進(jìn)行進(jìn)一步處理。
//CallerInfo.java public static CallerInfo getCallerInfo(Context context, Uri contactRef, Cursor cursor) { CallerInfo info = new CallerInfo(); if (cursor != null) { if (cursor.moveToFirst()) { columnIndex = cursor.getColumnIndex(PhoneLookup.LOOKUP_KEY); if (columnIndex != -1) { info.lookupKey = cursor.getString(columnIndex); } info.contactExists = true; } cursor.close(); cursor = null; } info.needUpdate = false; info.name = normalize(info.name); info.contactRefUri = contactRef; return info; }
系統(tǒng)原生的邏輯是取搜索結(jié)果的第一個(gè)記錄,并用來(lái)實(shí)例化。當(dāng)客戶需求改變,需要匹配不同號(hào)碼的時(shí)候,就需要修改這個(gè)地方的了。最優(yōu)先是遍歷整個(gè)cursor集合,并且根據(jù)客戶需求選出適合的結(jié)果,賦值給CallerInfo實(shí)例。
下面是整個(gè)號(hào)碼匹配的流程圖:
Call.java會(huì)將查詢后的結(jié)果設(shè)置到Call實(shí)例里面,并將其傳送到CallsManager里面進(jìn)行后續(xù)處理。而這個(gè)CallsManager會(huì)將這個(gè)Call顯示給客戶。
當(dāng)網(wǎng)絡(luò)端來(lái)電時(shí),frame層會(huì)接收到,并且連接成功之后會(huì)觸發(fā)Call.java里面的handleCreateConnectionSuccess。這個(gè)函數(shù)邏輯是從數(shù)據(jù)庫(kù)中查詢復(fù)合要求的聯(lián)系人,并且只取結(jié)果集的第一條記錄,用來(lái)初始化這個(gè)Call里面的變量。而后將這個(gè)Call傳到CallsManager進(jìn)行處理,顯示給用戶。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android 搜索結(jié)果匹配關(guān)鍵字且高亮顯示功能
- Android實(shí)現(xiàn)自動(dòng)匹配關(guān)鍵字并且標(biāo)紅功能
- Android Java實(shí)現(xiàn)余弦匹配算法示例代碼
- Android中的Intent Filter匹配規(guī)則簡(jiǎn)介
- 詳解Android中Intent對(duì)象與Intent Filter過(guò)濾匹配過(guò)程
- Android編程中號(hào)碼匹配位數(shù)修改的方法
- 從Android源碼剖析Intent查詢匹配的實(shí)現(xiàn)
- Android實(shí)現(xiàn)動(dòng)態(tài)自動(dòng)匹配輸入內(nèi)容功能
相關(guān)文章
Android防止按鈕過(guò)快點(diǎn)擊造成多次事件的解決方法
這篇文章主要介紹了Android防止按鈕過(guò)快點(diǎn)擊造成多次事件的解決方法的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-07-07Android實(shí)現(xiàn)基于滑動(dòng)的SQLite數(shù)據(jù)分頁(yè)加載技術(shù)(附demo源碼下載)
這篇文章主要介紹了Android實(shí)現(xiàn)基于滑動(dòng)的SQLite數(shù)據(jù)分頁(yè)加載技術(shù),涉及Android針對(duì)SQLite數(shù)據(jù)的讀取及查詢結(jié)果的分頁(yè)顯示功能相關(guān)實(shí)現(xiàn)技巧,末尾還附帶demo源碼供讀者下載參考,需要的朋友可以參考下2016-07-07Android動(dòng)畫之逐幀動(dòng)畫(Frame Animation)實(shí)例詳解
這篇文章主要介紹了Android動(dòng)畫之逐幀動(dòng)畫(Frame Animation),結(jié)合實(shí)例形式較為詳細(xì)的分析了逐幀動(dòng)畫的原理,注意事項(xiàng)與相關(guān)使用技巧,需要的朋友可以參考下2016-01-01Android開(kāi)發(fā)之ClipboardManager剪貼板功能示例
這篇文章主要介紹了Android開(kāi)發(fā)之ClipboardManager剪貼板功能,結(jié)合簡(jiǎn)單實(shí)例形式分析了Android使用ClipboardManager實(shí)現(xiàn)剪貼板功能的相關(guān)操作技巧,需要的朋友可以參考下2017-03-03Android實(shí)現(xiàn)ListView異步加載的方法(改進(jìn)版)
這篇文章主要介紹了Android實(shí)現(xiàn)ListView異步加載的方法,針對(duì)前面介紹的方法進(jìn)行了線程操作的改進(jìn),具有一定參考借鑒價(jià)值,需要的朋友可以參考下2016-08-08Android應(yīng)用中使用DOM方式解析XML格式數(shù)據(jù)的基本方法
這篇文章主要介紹了Android應(yīng)用中使用DOM方式解析XML格式數(shù)據(jù)的基本方法,值得注意的是DOM方式解析的效率并不高,在數(shù)據(jù)量大的時(shí)候并不推薦使用,需要的朋友可以參考下2016-04-04