通话中联系人信息查询用到的两个类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
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; //是否是语音邮箱号码
/**
* 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标记名称和头像为系统资源。
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;
...
}
...
}
@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();
}
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();
}
}
}
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触发一次回调。
mCallerInfo = CallerInfo.getCallerInfo(mContext, mQueryUri, cursor, cw.subId);
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); //头像查询完毕
}
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;
...
}
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); //开始获取头像,这个也是异步的
...
}
@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);
}
设置头像并清除所有回调。