Android6.0来电号码与电话薄联系人进行匹配
本文将介绍系统接收到来电之后,如何在电话薄中进行匹配联系人的流程。分析将从另外一篇文章(基于android6.0的ril框架层模块分析)中提到的与本文内容相关的代码开始。
//packages/service/***/call.java public void handlecreateconnectionsuccess( callidmapper idmapper, parcelableconnection connection) { sethandle(connection.gethandle(), connection.gethandlepresentation());//这个函数很重要,会启动一个查询 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())); } }
这个sethandle函数如下:
//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的那个runnable。这个就是启动查询号码的逻辑了。这个mcallerinfoasyncqueryfactory的赋值的流程比较曲折。在telecomservice被连接上调用onbind的时候,会调用initializetelecomsystem函数。那这个telecomservice是在哪里被启动的呢?在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) {//这个在系统启动阶段就会触发 if (phase == phase_activity_manager_ready) { connecttotelecom(); }}
所以从这里看,在系统启动阶段就会触发telecomservice这个service,且在成功连接到服务之后,将调用servicemanager.addservice(context.telecom_service, service),将这个服务添加到系统服务中了。这个类的构造函数中,在调用函数initializetelecomsystem初始化telecomsystem时,就实例化了一个内部匿名对象,并且在telecomsystem的构造函数中初始化一个mcallsmanager时将该匿名对象传入,而在callsmanager的processincomingcallintent中会用这个函数初始化一个call对象。所以这个mcallerinfoasyncqueryfactory的实际内容见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() {}));
可以看到,通过startquery来查询传入的number的动作。我们来看看callerinfoasyncquery的startquery函数。
//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; }
注意看注释,该函数还会对sip号码(包含@的号码)进行处理,还有紧急号码和语音邮箱号码进行区分。实际上,当对一个号码进行查询的时候,这三个startquery都用到了。注意,上面的startquery会根据结果对connection的值进行修改。
其中将号码转换成uri格式的数据,后续会对这个数据进行查询:
//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; }
这个函数里面的contactref的值应该是“content://com.android.contacts/phone_lookup_enterprise/13678909678/sip?”类似的。
实际上这个query是调用callerinfoasyncqueryhandler的startquery函数,而这个函数是直接调用它的父类asyncqueryhandler的同名函数。
//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); }
这个mworkerthreadhandler是在callerinfoasyncqueryhandler函数覆写父类的createhandler函数中赋值,是callerinfoworkerhandler类型。所以后续的处理函数是该类的handlemessage函数。
//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: }}}}
这个super就是asyncqueryhandler的内部类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(); }}
可以看到流程就是简单的用resolver.query来查询指定的query uri,然后将返回值通过消息机制发送到asyncqueryhandler的handlemessage里面处理,而在这里会调用callerinfoasyncquery的onquerycomplete函数。注意这个contentresolver是在uri上查询结果,而这个uri是由某个contentprovider来提供的。注意这个地址里面的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>
所以最后这个查询是由contactsprovider来执行的。
我们来看看查询完成之后,调用callerinfoasyncquery的onquerycomplete函数的具体流程:
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非常重要。在这里面会使用查询处理的cursor结果,并将合适的结果填充到mcallerinfo,将其传递到cw.listener.onquerycomplete函数中,作为最终结果进行进一步处理。
//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; }
系统原生的逻辑是取搜索结果的第一个记录,并用来实例化。当客户需求改变,需要匹配不同号码的时候,就需要修改这个地方的了。最优先是遍历整个cursor集合,并且根据客户需求选出适合的结果,赋值给callerinfo实例。
下面是整个号码匹配的流程图:
call.java会将查询后的结果设置到call实例里面,并将其传送到callsmanager里面进行后续处理。而这个callsmanager会将这个call显示给客户。
当网络端来电时,frame层会接收到,并且连接成功之后会触发call.java里面的handlecreateconnectionsuccess。这个函数逻辑是从数据库中查询复合要求的联系人,并且只取结果集的第一条记录,用来初始化这个call里面的变量。而后将这个call传到callsmanager进行处理,显示给用户。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。