通话中联系人信息CallerInfo查询

    xiaoxiao2021-03-25  116

    通话中联系人信息查询用到的两个类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实际就是numberLabel public int photoResource;//联系人头像资源id,例如紧急号码使用系统内置资源做头像 public long contactIdOrZero;//联系人数据库中id public boolean needUpdate;//标记callerinfo需要更新 public Uri contactRefUri;//联系人uri public String lookupKey;//查找联系人用,使用它比id查找效率高 public Uri contactDisplayPhotoUri;//联系人头像uri public Uri contactRingtoneUri;//铃声uri public 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 { ... @Override public 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: @Override public 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 properly Message msg = mWorkerThreadHandler.obtainMessage(token); msg.arg1 = EVENT_ARG_QUERY; WorkerArgs args = new WorkerArgs(); args.handler = this; ... mWorkerThreadHandler.sendMessage(msg); }handler就是查询时候传递进去的this,this的消息处理方法见: @Override public 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_LISTENER startQuery(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生成相应uri CallerInfoAsyncQuery c = new CallerInfoAsyncQuery(); c.allocate(context, contactRef); //初始化相应数据结构,allocate和查询完毕的release方法对应 //create cookieWrapper, start query CookieWrapper 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, // cookie contactRef, // uri null, // projection null, // selection null, // selectionArgs null); // orderBy return 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 callback if (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 NotificationMananger public 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; } @Override public 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。 @Override public 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); }设置头像并清除所有回调。
    转载请注明原文地址: https://ju.6miu.com/read-21815.html

    最新回复(0)