欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2024-03-04 20:05:30
本文将介绍系统接收到来电之后,如何在电话薄中进行匹配联系人的流程。分析将从另外一篇文章(基于android6.0的ril框架层模块分析)中提到的与本文内容相关的代码开始。...

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

下面是整个号码匹配的流程图:

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

call.java会将查询后的结果设置到call实例里面,并将其传送到callsmanager里面进行后续处理。而这个callsmanager会将这个call显示给客户。

当网络端来电时,frame层会接收到,并且连接成功之后会触发call.java里面的handlecreateconnectionsuccess。这个函数逻辑是从数据库中查询复合要求的联系人,并且只取结果集的第一条记录,用来初始化这个call里面的变量。而后将这个call传到callsmanager进行处理,显示给用户。

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