通话中联系人信息查询用到的两个类CallerInfoAsyncQuery和CallerInfo,这两个类都在frameworks/base/telephony下

frameworks/base/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java

frameworks/base/telephony/java/com/android/internal/telephony/CallerInfo.java

CallerInfo

    public String name;//名字public String phoneNumber;//号码public String normalizedNumber;//E.164标准格式的号码public String geoDescription;//地理位置,归属地public String cnapName;//接下来的三个值都是cnap相关,并不在数据库中存储。public int numberPresentation;//public int namePresentation;//public boolean contactExists;//联系人是否存在public String phoneLabel;//号码标记,例如手机,单位,传真....,依据numberType和numberLabel得来public int    numberType;//号码类型的常量值,例如手机是2,在ContactsContract中定义public String numberLabel;//numberType值为0(TYPE_CUSTOM)或者19(TYPE_ASSISTANT),phoneLabel实际就是numberLabelpublic int photoResource;//联系人头像资源id,例如紧急号码使用系统内置资源做头像public long contactIdOrZero;//联系人数据库中idpublic boolean needUpdate;//标记callerinfo需要更新public Uri contactRefUri;//联系人uripublic String lookupKey;//查找联系人用,使用它比id查找效率高public Uri contactDisplayPhotoUri;//联系人头像uripublic Uri contactRingtoneUri;//铃声uripublic boolean shouldSendToVoicemail;//标记号码是否直接转到语音邮箱,如果需要的话app层直接挂断处理public Drawable cachedPhoto;//联系人头像文件public Bitmap cachedPhotoIcon;//联系人头像icon,小一些,用于通知等需要小图片的地方public boolean isCachedPhotoCurrent;//cachedPhoto是否被初始化private boolean mIsEmergency;//是否是紧急号码private boolean mIsVoiceMail; //是否是语音邮箱号码
CNAP百度百科解释: Calling Name Presentation,这种业务是用户在申请这项业务时,可以向业务部门提交一份号码与名字的对照表。在这个号码与名字对照的信息输入移动通信系统后,如果这个用户呼叫其它号码时,就在呼叫接通振铃时,被叫用户的手机或终端上就会显示来话者的名字。

cnapName,numberPresentation,namePresentation比较特殊,不是在数据库中存储的,这个是来电时候网络上报的。numberPresentation和namePresentation的值在TelecomManager定义。cnapName为用户名,传给被叫方显示。

frameworks/base/telecomm/java/android/telecom/TelecomManager.java
  /*** Indicates that the address or number of a call is allowed to be displayed for caller ID.*/public static final int PRESENTATION_ALLOWED = 1; //可见/*** Indicates that the address or number of a call is blocked by the other party.*/public static final int PRESENTATION_RESTRICTED = 2; //不可见/*** Indicates that the address or number of a call is not specified or known by the carrier.*/public static final int PRESENTATION_UNKNOWN = 3; //未知,作用基本同PRESENTATION_RESTRICTED/*** Indicates that the address or number of a call belongs to a pay phone.*/public static final int PRESENTATION_PAYPHONE = 4; //公用电话

CallerInfo中重要的方法有两个:

  public static CallerInfo getCallerInfo(Context context, Uri contactRef, Cursor cursor, int subId) 
  package */ CallerInfo markAsEmergency(Context context)

getCallerInfo依据cursor填充callerinfo中的各个成员,markAsEmergency标记名称和头像为系统资源。

CallerInfoAsyncQuery

CallerInfoAsyncQueryHandler

该类是内部类,继承自frameworks/base/core/java/android/content/AsyncQueryHandler.java,负责完成异步查询。

    public AsyncQueryHandler(ContentResolver cr) {super();mResolver = new WeakReference<ContentResolver>(cr);synchronized (AsyncQueryHandler.class) {if (sLooper == null) {HandlerThread thread = new HandlerThread("AsyncQueryWorker");thread.start();sLooper = thread.getLooper();}}mWorkerThreadHandler = createHandler(sLooper);}

AsyncQueryHandler构造函数中的sLooper的初始化是关键,使用了HandlerThread,这样sLooper就不是主线程队列了,是一个线程队列。

        protected Handler createHandler(Looper looper) {return new CallerInfoWorkerHandler(looper);}

createHandler被子类重写,

protected class CallerInfoWorkerHandler extends WorkerHandler {...@Overridepublic void handleMessage(Message msg) {...switch (cw.event) {case EVENT_NEW_QUERY://start the sql command.super.handleMessage(msg);break;...}...
}
调用父类的消息处理方法,注意EVENT_NEW_QUERY和EVENT_ARG_QUERY值都是1:
        @Overridepublic void handleMessage(Message msg) {final ContentResolver resolver = mResolver.get();if (resolver == null) return;WorkerArgs args = (WorkerArgs) msg.obj;int token = msg.what;int event = msg.arg1;switch (event) {case EVENT_ARG_QUERY:...cursor = resolver.query(args.uri, args.projection,args.selection, args.selectionArgs,args.orderBy);...args.result = cursor;break;...}Message reply = args.handler.obtainMessage(token);reply.obj = args;reply.arg1 = msg.arg1;reply.sendToTarget();}

这样查询的代码就运行在线程中。查询完毕回调怎么触发?AsyncQueryHandler继承自Handler,那么WorkHandler是工作线程,自身的消息队列就是用于通知回调的,上述代码中的args.handler就是自身的消息队列:

  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 properlyMessage msg = mWorkerThreadHandler.obtainMessage(token);msg.arg1 = EVENT_ARG_QUERY;WorkerArgs args = new WorkerArgs();args.handler = this;...mWorkerThreadHandler.sendMessage(msg);}

handler就是查询时候传递进去的this,this的消息处理方法见:

 @Overridepublic void handleMessage(Message msg) {WorkerArgs args = (WorkerArgs) msg.obj;if (localLOGV) {Log.d(TAG, "AsyncQueryHandler.handleMessage: msg.what=" + msg.what+ ", msg.arg1=" + msg.arg1);}int token = msg.what;int event = msg.arg1;// pass token back to caller on each callback.switch (event) {case EVENT_ARG_QUERY:onQueryComplete(token, args.cookie, (Cursor) args.result);break;...}}

消息处理中调用onQueryComplete,这个是CallerInfoAsyncQueryHandler中实现的:

    protected void onQueryComplete(int token, Object cookie, Cursor cursor) {...if (cw.event == EVENT_END_OF_QUEUE) { //EVENT_END_OF_QUEUE表示整个查询流程的结束release();if (cursor != null) {cursor.close();}return;}// check the token and if needed, create the callerinfo object.if (mCallerInfo == null) {...if (cw.event == EVENT_EMERGENCY_NUMBER) {// Note we're setting the phone number here (refer to javadoc// comments at the top of CallerInfo class).mCallerInfo = new CallerInfo().markAsEmergency(mContext); //标记为紧急号码} else if (cw.event == EVENT_VOICEMAIL_NUMBER) {mCallerInfo = new CallerInfo().markAsVoiceMail(cw.subId); //标记为语音邮箱号码} else {/// M: CC001: CallerInfo OP Plugin @{//According to CallerInfoExt implementation on L, subId is requested for USIM AAS feature.//mCallerInfo = CallerInfo.getCallerInfo(mContext, mQueryUri, cursor);mCallerInfo = CallerInfo.getCallerInfo(mContext, mQueryUri, cursor, cw.subId); //这个就是之前讲的方法,依据cursor填充callerinfo/// @}if (DBG) Rlog.d(LOG_TAG, "==> Got mCallerInfo: " + mCallerInfo);CallerInfo newCallerInfo = CallerInfo.doSecondaryLookupIfNecessary(mContext, cw.number, mCallerInfo); //sip通话才有可能走这里,一般情况下当这个是空方法if (newCallerInfo != mCallerInfo) {mCallerInfo = newCallerInfo;if (DBG) Rlog.d(LOG_TAG, "#####async contact look up with numeric username"+ mCallerInfo);}// Final step: look up the geocoded description.if (ENABLE_UNKNOWN_NUMBER_GEO_DESCRIPTION) {   //更新归属地,不过这个默认实现比较粗糙,国内还是用三方归属地查询的多...mCallerInfo.updateGeoDescription(mContext, cw.number);...}// Use the number entered by the user for display.if (!TextUtils.isEmpty(cw.number)) {mCallerInfo.phoneNumber = PhoneNumberUtils.formatNumber(cw.number,mCallerInfo.normalizedNumber,CallerInfo.getCurrentCountryIso(mContext));}}if (DBG) Rlog.d(LOG_TAG, "constructing CallerInfo object for token: " + token);//notify that we can clean up the queue after this.CookieWrapper endMarker = new CookieWrapper();endMarker.event = EVENT_END_OF_QUEUE; //流程结束,这里不直接release的原因是后面还可能有消息要处理,例如EVENT_ADD_LISTENERstartQuery(token, endMarker, null, null, null, null, null);}//notify the listener that the query is complete.if (cw.listener != null) {if (DBG) Rlog.d(LOG_TAG, "notifying listener: " + cw.listener.getClass().toString() +" for token: " + token + mCallerInfo);cw.listener.onQueryComplete(token, cw.cookie, mCallerInfo); //通知Listener,触发回调}if (cursor != null) {cursor.close();}}}

查询

查询的方法可以看成有两个,一个是依据contacts uri查询,另外一个是依据number查询,这里分析依据号码查询的方法。
    public static CallerInfoAsyncQuery startQuery(int token, Context context, String number,OnQueryCompleteListener listener, Object cookie, int subId) {final Uri contactRef = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.buildUpon().appendPath(number).appendQueryParameter(PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS,String.valueOf(PhoneNumberUtils.isUriNumber(number))).build(); //依据number生成相应uriCallerInfoAsyncQuery c = new CallerInfoAsyncQuery();c.allocate(context, contactRef);  //初始化相应数据结构,allocate和查询完毕的release方法对应//create cookieWrapper, start queryCookieWrapper cw = new CookieWrapper(); //生成查询方法中传入的cookie对象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./// M: CC003: Query ECC via EmergencyNumberExt @{int phoneType = TelephonyManager.getDefault().getCurrentPhoneType(cw.subId);if (PhoneNumberUtils.isEmergencyNumberExt(number, phoneType)) { //依据号码类型传递不同的event值,如果是紧急号码根本就不必去数据库中查询了/// @}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,  // cookiecontactRef,  // urinull,  // projectionnull,  // selectionnull,  // selectionArgsnull);  // orderByreturn c;}

添加回调

    public void addQueryListener(int token, OnQueryCompleteListener listener, Object cookie) {if (DBG) Rlog.d(LOG_TAG, "adding listener to query: " + sanitizeUriToString(mHandler.mQueryUri) +" handler: " + mHandler.toString());//create cookieWrapper, add query request to end of queue.CookieWrapper cw = new CookieWrapper();cw.listener = listener;cw.cookie = cookie;cw.event = EVENT_ADD_LISTENER;mHandler.startQuery(token, cw, null, null, null, null, null);}

发送EVENT_ADD_LISTENER消息,最终会回到CallerInfoAsyncQueryHandler的onQueryComplete触发一次回调。

这个要对消息队列理解清楚。例如app代码中先是查询,然后添加了2个Listener,那么CallerInfoAsyncQueryHandler的的工作线程要依次处理EVENT_NEW_QUERY、EVENT_ADD_LISTENER、EVENT_ADD_LISTENER三个消息;主线程依次要处理三个EVENT_NEW_QUERY消息,这三个消息都调用了onQueryComplete;对onQueryComplete来说首先处理EVENT_NEW_QUERY消息得到了CallerInfo并往主线程队列投递了EVENT_END_OF_QUEUE消息,然后两次处理EVENT_ADD_LISTENER触发listener的回调,最后处理EVENT_END_OF_QUEUE结束整个流程。
注意onQueryComplete中的这句:
   mCallerInfo = CallerInfo.getCallerInfo(mContext, mQueryUri, cursor, cw.subId);

当EVENT_ADD_LISTENER消息处理走到这的时候cursor是null,但是getCallerInfo在cursor为null的时候不会有任何动作,这个算是一个trick,因为一般理解cursor为null的时候mCallerInfo对应也该置null才对。
从这个流程也看出addQueryListener一定要在startQuery语句后调用。

InCallUI中的联系人查询

packages/apps/InCallUI/src/com/android/incallui/CallerInfo.java

packages/apps/InCallUI/src/com/android/incallui/CallerInfoAsyncQuery.java
mtk的InCallUI代码中也有CallerInfo和CallerInfoAsyncQuery两个类,直接就是拷贝framework中的代码,这个就是做app的为了不修改framework常用的方法,理论上肯定不推荐,但是这也是很多公司代码权限和编译管理太蛋疼的结果。

ContactInfoCache

InCallUI中重要的类,作用是联系人信息的缓存,在UI刷新中多次要获取联系人信息,如果每次都是去数据库查会影响效率。
packages/apps/InCallUI/src/com/android/incallui/ContactInfoCache.java
    public static synchronized ContactInfoCache getInstance(Context mContext) {if (sCache == null) {sCache = new ContactInfoCache(mContext.getApplicationContext());}return sCache;}

单例模式,方便其它地方调用

  public void findInfo(final Call call, final boolean isIncoming,ContactInfoCacheCallback callback) {Preconditions.checkState(Looper.getMainLooper().getThread() == Thread.currentThread());Preconditions.checkNotNull(callback);final String callId = call.getId();final ContactCacheEntry cacheEntry = mInfoMap.get(callId); //mInfoMap是缓存Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);if (cacheEntry != null) {         callback.onContactInfoComplete(callId, cacheEntry); //如果缓存有值,直接触发回调并返回if (callBacks == null) {return;}}// If the entry already exists, add callbackif (callBacks != null) {  //如果正在查询,添加callback并返回callBacks.add(callback);return;}callBacks = new CopyOnWriteArraySet<ContactInfoCacheCallback>();     callBacks.add(callback);mCallBacks.put(callId, callBacks);     mCallBackCancel.put(callId, false);final CallerInfo callerInfo = CallerInfoUtils.getCallerInfoForCall(mContext, call, new FindInfoCallback(isIncoming)); //开始查询findInfoQueryComplete(call, callerInfo, isIncoming, false);}

其它代码要获取信息的入口,传递的回调定义如下:

    public interface ContactInfoCacheCallback {public void onContactInfoComplete(String callId, ContactCacheEntry entry); //信息查询完毕public void onImageLoadComplete(String callId, ContactCacheEntry entry);  //头像查询完毕}
返回的结构数据结构ContactCacheEntry如下:
public static class ContactCacheEntry {public String name;public String number;public String location;public String label;public Drawable photo;public boolean isSipCall;public Uri contactUri;public Uri displayPhotoUri;public Uri lookupUri; // Sent to NotificationManangerpublic String lookupKey;...}
成员看字面意思就可以了解意思了,已经详细介绍过的callerinfo中的成员比这个还多。cnap等都已经处理完毕,这个就是最后要呈现到UI上的元素,这里看出ContactCacheEntry是把CallerInfo又封装了一层。
开始查询的getCallerInfoForCall定义如下:
  public static CallerInfo getCallerInfoForCall(Context context, Call call,CallerInfoAsyncQuery.OnQueryCompleteListener listener) {CallerInfo info = buildCallerInfo(context, call);if (info.numberPresentation == TelecomManager.PRESENTATION_ALLOWED) {CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, info, listener, call);}return info;}

调用CallerInfoAsyncQuery发起查询,这个是异步的,回调是:

  private class FindInfoCallback implements CallerInfoAsyncQuery.OnQueryCompleteListener {private final boolean mIsIncoming;public FindInfoCallback(boolean isIncoming) {mIsIncoming = isIncoming;}@Overridepublic void onQueryComplete(int token, Object cookie, CallerInfo callerInfo) {        ...findInfoQueryComplete((Call) cookie, callerInfo, mIsIncoming, true);...}}

调用findInfoQueryComplete:

     private void findInfoQueryComplete(Call call, CallerInfo callerInfo, boolean isIncoming,boolean didLocalLookup) {...mInfoMap.put(callId, cacheEntry); //缓存信息...sendInfoNotifications(callId, cacheEntry); //触发回调...ContactsAsyncHelper.startObtainPhotoAsync(TOKEN_UPDATE_PHOTO_FOR_CALL_STATE,mContext, cacheEntry.displayPhotoUri, ContactInfoCache.this, callId); //开始获取头像,这个也是异步的...}
缓存信息并发起获取联系人头像的请求,因为数据库表中存储的是头像的uri。
   @Overridepublic void onImageLoadComplete(int token, Drawable photo, Bitmap photoIcon, Object cookie) {final String callId = (String) cookie;final ContactCacheEntry entry = mInfoMap.get(callId);if (entry == null) {Log.e(this, "Image Load received for empty search entry.");clearCallbacks(callId);return;}Log.d(this, "setting photo for entry: ", entry);// Conference call icons are being handled in CallCardPresenter.if (photo != null) {Log.v(this, "direct drawable: ", photo);entry.photo = photo;} else if (photoIcon != null) {Log.v(this, "photo icon: ", photoIcon);entry.photo = new BitmapDrawable(mContext.getResources(), photoIcon);} else {Log.v(this, "unknown photo");entry.photo = null;}sendImageNotifications(callId, entry); //触发头像回调clearCallbacks(callId);}

设置头像并清除所有回调。

通话中联系人信息CallerInfo查询相关推荐

  1. 最快速读取手机通讯录中联系人信息

    作为一名Android开发,读写手机通讯录的操作人人都会,但是有没有遇到通讯录存在好几百条联系人信息时候读取的速度会明显变慢呢?本文就是介绍解决办法,我总结出了以下几种办法 提供参考: 一.线程 有A ...

  2. 读取SIM卡中联系人流程

    本文主要讲USIM卡插入手机后读取卡中contacts信息的流程. 整体流程总结 每次插拔SIM卡都会将联系人数据库中关于SIM卡的联系人删除 SimStateReceiver 通过接收 RIL 上报 ...

  3. Android中获取手机中的联系人信息

    #Android中获取手机中的联系人信息 1.0.查看系统通讯录的表,表路径:data->data->com.android.providers.contacts->database ...

  4. PYthon作业通讯录文件中存有若干联系人的信息,每个联系人的信息由姓名和电话号码组成。 编写程序,完成以下功能: 输入姓名,若通讯录文件中存在,则讲该联系人信息输出;

    """通讯录文件中存有若干联系人的信息,每个联系人的信息由姓名和电话号码组成.编写程序,完成以下功能:输入姓名,若通讯录文件中存在,则讲该联系人信息输出:若不存在,则输出 ...

  5. android 号码查联系人,android查询系统联系人信息

    转自http://www.cnblogs.com/error404/archive/2013/03/12/2956090.html 在android中读取联系人信息的程序,包括读取联系人姓名.手机号码 ...

  6. sql server 统计_看SQL Server 2016中的新实时查询统计信息

    sql server 统计 With the release of SQL Server 2016 also comes a great new feature to get a live view ...

  7. 查询各部门中工资最低的雇员信息(不要使用int关键字)、查询工资最高的雇员信息、查询工资高于部门20中所有员工的雇员信息、查询负责管理其他雇员的管理员信息

    数据是Oracle数据库默认的数据 各个字段含义:雇员编号,部门,职位,管理者编号,入职日期,工资,奖金,部门id 要求查询工资最高的雇员信息. -- 要求查询工资最高的雇员信息. select ma ...

  8. Android 获取通讯录联系人,打开通讯录获取联系人信息;整个流程封装在基类中;

    打开原生通讯录获取联系人姓名和手机号 1.获取通讯录权限: <!--访问通讯录--><uses-permission android:name="android.permi ...

  9. mysql中通过sql语句查询指定数据表的字段信息

      mysql数据库在安装完成时,自动创建了information_schema.mysql.test这三个数据库.其中,information_schema记录了创建的所有数据库的相关信息,因此可以 ...

最新文章

  1. 哈啰顺风车成立5亿元“顺风绿色出行基金”
  2. Oracle创建表空间、创建用户以及授权、查看权限
  3. 日本推出机器人代理相亲,相亲现场帮你自我介绍
  4. 测验2: Python基本图形绘制 (第2周)
  5. 【颜值打分小程序】最火爆的“颜值测试”,做还是不做?(疯狂打call)
  6. 为myeclipse分配更大的内存
  7. 项目常用第三方库 Swift版
  8. 搜索引擎分布式爬虫介绍
  9. mysql max datetime_MYSQL在联接语句中选择MAX日期
  10. 卡拉丁发布第四代车用空调滤清器
  11. 工作76::一直报400
  12. “独裁者”Google:开发者别无他选!| 极客头条
  13. 博文视点大讲堂35期《Google Android创赢路线与产品开发实战》读者见面会
  14. PHP-redis中文帮助手册_set相关
  15. java jeditorpane 自动换行_java – JTextPane JScrollPane自动换行?
  16. input输入身份证验证
  17. 王家林人工智能AI第15课:通过SVM进一步改进在Social Network上销售汽车推荐系统的精准性 老师微信13928463918
  18. mysql audit plugin_MySQL Audit Plugin的简单应用
  19. web 中怎么实现斜线表头效果?
  20. charles软件关闭后浏览器无法上网的问题

热门文章

  1. python爬取地图地址_用Python抓取百度地图里的店名,地址和联系方式
  2. 谷歌地图的key的获取
  3. c语言输出字符串缩进,C语言printf()和puts()的简单使用
  4. Docker网络和端口映射
  5. Photoshop脚本入门(三)- ExtendScript 核心技术
  6. 谈恋爱可以让生命更富激情
  7. 通用串行总线USB接口——基础总结(USB版本演进、接口类型、电气特性、拓扑结构、USB硬件接口实现)
  8. 淘宝API item_search_guang - 爱逛街
  9. XFS:kmem_alloc中可能的内存分配死锁
  10. 中文经过hibernate处理变成乱码