当前位置: 移动技术网 > 移动技术>移动开发>Android > Android6.0来电号码与电话薄联系人进行匹配

Android6.0来电号码与电话薄联系人进行匹配

2019年07月24日  | 移动技术网移动技术  | 我要评论

本文将介绍系统接收到来电之后,如何在电话薄中进行匹配联系人的流程。分析将从另外一篇文章(基于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进行处理,显示给用户。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持移动技术网。

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网