Android 6.0 Phone"通话显示"查询流程
来源:互联网 发布:chart.js zoom y axis 编辑:程序博客网 时间:2024/06/05 08:14
一、概要
无论是在MT (Mobile Termination Call被叫——来电),还是MO (Mobile Origination Call主叫——去电) 流程中,通话界面上都会显示当前通话的名称( 后文以displayName指代 )。通常情况下,如果是一个陌生号码,则会显示为该陌生号码。如果是已知联系人,则会显示该联系人的名称。当然,在会议电话( Conference Call )的情况下则直接显示”会议电话”。但是,在某些特殊情况下,displayName还会显示诸如”私人号码”、”公用电话”、”未知号码”等。
本文主要分析displayName的获取显示流程及显示”未知号码”的原因
二、查询流程
1、开始查询——CallCardPresenter
displayName是隶属于CallCardFragment的控件,当通话MO/MT流程发起时InCallActivity会显示,此时将会触发CallCardFragment界面更新,在CallCardPresenter的init方法中查询displayName,关键代码如下:
CallCardPresenter.java (\packages\apps\incallui\src\com\android\incallui)
public void init(Context context, Call call) { // Call may be null if disconnect happened already. if (call != null) { mPrimary = call; // start processing lookups right away. if (!call.isConferenceCall()) { startContactInfoSearch(call, CallEnum.PRIMARY, call.getState() == Call.State.INCOMING); } else { /// M: Modified this for MTK DSDA feature. @{ /* Google Code: updateContactEntry(null, true); */ updateContactEntry(null, CallEnum.PRIMARY, true); /// @} } } }
startContactInfoSearch的具体代码如下:
/** * Starts a query for more contact data for the save primary and secondary calls. */ private void startContactInfoSearch(final Call call, CallEnum type, boolean isIncoming) { final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);// ContactInfoCache中开始查找 cache.findInfo(call, isIncoming, new ContactLookupCallback(this, type)); }
2、异步查询——ContactInfoCache
在CallCardPresenter中发起查询之后会跳转到ContactInfoCache.findInfo()方法中,ContactInfoCache不仅用于查询当前通话的相关信息,还可以将这些信息缓存以备下次查询相同信息时快速返回。findInfo关键代码如下:
/** * Requests contact data for the Call object passed in. * Returns the data through callback. If callback is null, no response is made, however the * query is still performed and cached. * * @param callback The function to call back when the call is found. Can be null. */ public void findInfo(final Call call, final boolean isIncoming, ContactInfoCacheCallback callback) {// 查询caller信息,完成之后会回调到FindInfoCallback中,会调用findInfoQueryComplete final CallerInfo callerInfo = CallerInfoUtils.getCallerInfoForCall( mContext, call, new FindInfoCallback(isIncoming));// 当查询完毕之后回调并更新ContactEntry,这里最终会去更新界面显示 findInfoQueryComplete(call, callerInfo, isIncoming, false); }
CallerInfo中包含了当前call的基本信息,比如号码、类型、特殊相关服务等,在获取到这些信息之后再进行进一步的联系人数据库查询。
3、获取CallerInfo——CallerInfoUtils
在getCallerInfoForCall()方法中,除了获取当前Call的基本信息之外,还会根据当前Call的phoneNumber去数据库中查询,关键代码如下:
ContactInfoCache.java (\packages\apps\incallui\src\com\android\incallui)
/** * This is called to get caller info for a call. This will return a CallerInfo * object immediately based off information in the call, but * more information is returned to the OnQueryCompleteListener (which contains * information about the phone number label, user's name, etc). */ public static CallerInfo getCallerInfoForCall(Context context, Call call, CallerInfoAsyncQuery.OnQueryCompleteListener listener) {// 获取当前Call的基本信息并创建CallerInfo对象 CallerInfo info = buildCallerInfo(context, call); // 根据phoneNumber在CallerInfoAsyncQuery中开启具体查询 if (info.numberPresentation == TelecomManager.PRESENTATION_ALLOWED) { // Start the query with the number provided from the call. Log.d(TAG, "==> Actually starting CallerInfoAsyncQuery.startQuery()..."); CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, info, listener, call); } return info; }
在以上代码中,有两个重要的方法,即buildCallerInfo()和CallerInfoAsyncQuery.startQuery(),先查询看buildCallerInfo()的关键代码:
public static CallerInfo buildCallerInfo(Context context, Call call) { CallerInfo info = new CallerInfo(); // Store CNAP information retrieved from the Connection (we want to do this // here regardless of whether the number is empty or not).// 获取当前Call的CNAP name info.cnapName = call.getCnapName(); info.name = info.cnapName; info.numberPresentation = call.getNumberPresentation(); info.namePresentation = call.getCnapNamePresentation(); String number = call.getNumber(); // 获取当前Call的number,如果不为空则执行 if (!TextUtils.isEmpty(number)) { final String[] numbers = number.split("&"); number = numbers[0]; if (numbers.length > 1) { info.forwardingNumber = numbers[1]; }// 针对CNAP的情况特殊处理number显示 number = modifyForSpecialCnapCases(context, info, number, info.numberPresentation); info.phoneNumber = number; } // Because the InCallUI is immediately launched before the call is connected, occasionally // a voicemail call will be passed to InCallUI as a "voicemail:" URI without a number. // This call should still be handled as a voicemail call. if ((call.getHandle() != null && PhoneAccount.SCHEME_VOICEMAIL.equals(call.getHandle().getScheme())) || isVoiceMailNumber(context, call)) { info.markAsVoiceMail(context); } return info; }
如果当前Call的被叫一方没有开通该业务,则cnapName的值返回为空。同时,在buildCallerInfo方法中也对当前Call的number是否为空做了判断。这里的number来自于网络侧的返回,比如作为主叫方,当通话接通后被叫方的号码会通过网络返回,在某些特殊的情况下返回值有可能为空。
注:关于CNAP
CNAP即Calling Name Presentation的缩写,是运营商提供的一种服务。比如,用户开通该服务后,在运营商处设置Calling Name Presentation为”HelloSeven”。当该用户与其他用户通话时,如果对方的手机支持CNAP功能,那么无论对方联系人里是否存入了该号码,displayName都会显示为”HelloSeven”。加拿大的一些运营商有使用该服务,比如Rogers,但目前国内的运营商均不支持该服务。
4、查询数据库——CallerInfoAsyncQuery
当buildCallerInfo()执行完成后,会根据当前Call的number查询本机Contacts数据库。这里以MTK双卡为例,因此会执行CallerInfoAsyncQuery.startQueryEx(QUERY_TOKEN, context, number,listener, call, call.getSlotId())方法,关键代码如下( frameworks/base/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java ):
CallerInfoAsyncQuery.java (\packages\apps\incallui\src\com\android\incallui)
/** * Factory method to start the query based on a CallerInfo object. * * 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, CallerInfo info, OnQueryCompleteListener listener, Object cookie) { // Construct the URI object and query params, and start the query. final Uri contactRef = PhoneLookup.ENTERPRISE_CONTENT_FILTER_URI.buildUpon() .appendPath(info.phoneNumber) .appendQueryParameter(PhoneLookup.QUERY_PARAMETER_SIP_ADDRESS, String.valueOf(PhoneNumberHelper.isUriNumber(info.phoneNumber))) .build(); CallerInfoAsyncQuery c = new CallerInfoAsyncQuery(); c.allocate(context, contactRef);//这里需要注意,allocate会对mHanlder对象进行赋值 //create cookieWrapper, start query CookieWrapper cw = new CookieWrapper(); cw.listener = listener; cw.cookie = cookie; cw.number = info.phoneNumber; // check to see if these are recognized numbers, and use shortcuts if we can.// 设置查询类型包括:EMERGENCY_NUMBER、VOICEMAIL、NEW_QUERY if (PhoneNumberUtils.isLocalEmergencyNumber(context, info.phoneNumber)) { cw.event = EVENT_EMERGENCY_NUMBER; } else if (info.isVoiceMailNumber()) { 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; }
以上代码中主要完成:设置查询对应的数据库表;设置查询类型(紧急号码、语音号码、普通查询);发起数据库查询。其中c.allocate()方法会对mHandler进行赋值,关键代码如下:
/** * Method to create a new CallerInfoAsyncQueryHandler object, ensuring correct * state of context and uri. */ private void allocate(Context context, Uri contactRef) { if ((context == null) || (contactRef == null)){ throw new QueryPoolException("Bad context or query uri."); } mHandler = new CallerInfoAsyncQueryHandler(context); mHandler.mQueryContext = context; mHandler.mQueryUri = contactRef; }
当执行c.mHandler.startQuery的时候,会先查询CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler中是否有startQuery方法,之后再跳转到父类AsyncQueryHandler的startQuery方法中( frameworks/base/core/java/android/content/AsyncQueryHandler.java ):
/** * This method begins an asynchronous query. When the query is done * {@link #onQueryComplete} is called. * * @param token A token passed into {@link #onQueryComplete} to identify * the query. * @param cookie An object that gets passed into {@link #onQueryComplete} * @param uri The URI, using the content:// scheme, for the content to * retrieve. * @param projection A list of which columns to return. Passing null will * return all columns, which is discouraged to prevent reading data * from storage that isn't going to be used. * @param selection A filter declaring which rows to return, formatted as an * SQL WHERE clause (excluding the WHERE itself). Passing null will * return all rows for the given URI. * @param selectionArgs You may include ?s in selection, which will be * replaced by the values from selectionArgs, in the order that they * appear in the selection. The values will be bound as Strings. * @param orderBy How to order the rows, formatted as an SQL ORDER BY * clause (excluding the ORDER BY itself). Passing null will use the * default sort order, which may be unordered. */ 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);//类型为EVENT_ARG_QUERY msg.arg1 = EVENT_ARG_QUERY; WorkerArgs args = new WorkerArgs(); //从CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler跳转到AsyncQueryHandler,因此这里的this是CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler对象 args.handler = this; args.uri = uri; args.projection = projection; args.selection = selection; args.selectionArgs = selectionArgs; args.orderBy = orderBy; args.cookie = cookie; msg.obj = args;//这里的mWorkerThreadHandler的实例在 mWorkerThreadHandler.sendMessage(msg); }
最后通过mWorkerThreadHandler.sendMessage()方法跳转到CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler.CallerInfoWorkerHandler的handleMessage方法中,关键代码如下:
protected class CallerInfoWorkerHandler extends WorkerHandler { public CallerInfoWorkerHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { WorkerArgs args = (WorkerArgs) msg.obj; CookieWrapper cw = (CookieWrapper) args.cookie;//这里的cw在startQueryEx中进行了设置,不为null 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. Log.d(this, "Unexpected command (CookieWrapper is null): " + msg.what + " ignored by CallerInfoWorkerHandler, passing onto parent."); super.handleMessage(msg); } else { Log.d(this, "Processing event: " + cw.event + " token (arg1): " + msg.arg1 + " command: " + msg.what + " query URI: " + sanitizeUriToString(args.uri)); switch (cw.event) {//此时event为NEW_QUERY case EVENT_NEW_QUERY: //start the sql command. super.handleMessage(msg); break; // shortcuts to avoid query for recognized numbers. case EVENT_EMERGENCY_NUMBER: case EVENT_VOICEMAIL_NUMBER: case EVENT_ADD_LISTENER: 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: } } } }
如果只是普通的号码查询,则执行case EVENT_NEW_QUERY,回调到父类AsyncQueryHandler.WorkerHandler的handleMessage方法中:
AsyncQueryHandler.java (alps\frameworks\base\core\java\android\content)
protected class WorkerHandler extends Handler { @Override public void handleMessage(Message msg) { switch (event) { case EVENT_ARG_QUERY: Cursor cursor; try {// 查询Contacts数据库中的PhoneLookup表 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(); } } catch (Exception e) { Log.w(TAG, "Exception thrown during handling EVENT_ARG_QUERY", e); cursor = null; }// 将查询结果保存到args中 args.result = cursor; break; case EVENT_ARG_INSERT: args.result = resolver.insert(args.uri, args.values); break; case EVENT_ARG_UPDATE: args.result = resolver.update(args.uri, args.values, args.selection, args.selectionArgs); break; case EVENT_ARG_DELETE: args.result = resolver.delete(args.uri, args.selection, args.selectionArgs); break; } // passing the original token value back to the caller // on top of the event values in arg1. //注意:这里的args.handler对象实际上是CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler的实例 Message reply = args.handler.obtainMessage(token); reply.obj = args; reply.arg1 = msg.arg1; if (localLOGV) { Log.d(TAG, "WorkerHandler.handleMsg: msg.arg1=" + msg.arg1 + ", reply.what=" + reply.what); } reply.sendToTarget(); } }
在WorkHandler中查询完毕之后,执行args.handler.obtainMessage(),这里的args.handler实际上是CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler的实例,但在CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler中并没有handleMessage方法,因此回调其父类AsyncQueryHandler的handleMessage方法:
@Override public void handleMessage(Message msg) { // pass token back to caller on each callback. switch (event) {// 查询完毕之后执行 case EVENT_ARG_QUERY: onQueryComplete(token, args.cookie, (Cursor) args.result); break; case EVENT_ARG_INSERT: onInsertComplete(token, args.cookie, (Uri) args.result); break; case EVENT_ARG_UPDATE: onUpdateComplete(token, args.cookie, (Integer) args.result); break; case EVENT_ARG_DELETE: onDeleteComplete(token, args.cookie, (Integer) args.result); break; } }
onQueryComplete方法可以看做this.onQueryComplete,而this来源于CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler。因此,这里会回调到CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler的onQueryComplete方法中,关键代码如下:
@Override protected void onQueryComplete(int token, Object cookie, Cursor cursor) { try { //get the cookie and notify the listener. CookieWrapper cw = (CookieWrapper) cookie; //notify the listener that the query is complete.//查询完毕,将查询结果返回. if (cw.listener != null) { Log.d(this, "notifying listener: " + cw.listener.getClass().toString() + " for token: " + token + mCallerInfo); cw.listener.onQueryComplete(token, cw.cookie, mCallerInfo); } } finally { // The cursor may have been closed in CallerInfo.getCallerInfo() if (cursor != null && !cursor.isClosed()) { cursor.close(); } } } }
以上代码中的cw.listener来自于CallerInfoAsyncQuery.startQueryEx(),在ContactInfoCache.findInfo()中可以看到,listener实际为ContactInfoCache.FindInfoCallback的对象:
final CallerInfo callerInfo = CallerInfoUtils.getCallerInfoForCall( mContext, identification, new FindInfoCallback(isIncoming));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) { final CallIdentification identification = (CallIdentification) cookie; findInfoQueryComplete(identification, callerInfo, mIsIncoming, true); }}
也就是说查询完成之后会回调到ContactInfoCache.FindInfoCallback的onQueryComplete()方法中,并执行ContactInfoCache.findInfoQueryComplete()。
5、完善查询结果——ContactInfoCache
经过各种回调之后,终于将查询结果返回到ContactInfoCache.findInfoQueryComplete方法中,该方法主要用于将查询结果封装为ContactCacheEntry对象,并发起查询完毕的回调,关键代码如下:
private void findInfoQueryComplete(CallIdentification identification, CallerInfo callerInfo, boolean isIncoming, boolean didLocalLookup) { //将查询结果保存到ContactCacheEntry中 final ContactCacheEntry cacheEntry = buildEntry(mContext, callId, callerInfo, presentationMode, isIncoming); //将cacheEntry对应与之相对的callId mInfoMap.put(callId, cacheEntry); if(!mExpiredInfoMap.containsKey(callId)) { //将查询完成的消息通知到相应的回调方法 sendInfoNotifications(callId, cacheEntry); } //... ...省略}
注意以下两点:
①. buildEntry()会将最终的显示内容准备好,以供后续使用;
②. sendInfoNotifications()发起回调,通知相关listener“查询完毕可供显示”;
查看buildEntry()的关键代码如下:
private ContactCacheEntry buildEntry(Context context, String callId, CallerInfo info, int presentation, boolean isIncoming) { // The actual strings we're going to display onscreen: Drawable photo = null;//构造ContatcCacheEntry final ContactCacheEntry cce = new ContactCacheEntry(); populateCacheEntry(context, info, cce, presentation, isIncoming); // This will only be true for emergency numbers if (info.photoResource != 0) { photo = context.getResources().getDrawable(info.photoResource); } else if (info.isCachedPhotoCurrent) { if (info.cachedPhoto != null) { photo = info.cachedPhoto; } else { photo = context.getResources().getDrawable(R.drawable.picture_unknown); photo.setAutoMirrored(true); } } else if (info.contactDisplayPhotoUri == null) { photo = context.getResources().getDrawable(R.drawable.picture_unknown); photo.setAutoMirrored(true); } else { cce.displayPhotoUri = info.contactDisplayPhotoUri; } //mod-start by depeng.li for bug 39488 on 2015.12.23 //if (info.lookupKeyOrNull == null || info.contactIdOrZero == 0) { // Log.v(TAG, "lookup key is null or contact ID is 0. Don't create a lookup uri."); // cce.lookupUri = null; //} else { cce.lookupUri = Contacts.getLookupUri(info.contactIdOrZero, info.lookupKeyOrNull); //} //mod-end by depeng.li for bug 39488 on 2015.12.23 cce.photo = photo; cce.lookupKey = info.lookupKeyOrNull; return cce; }
在buildEntry方法中,主要是调用populateCacheEntry()完成ContactCacheEntry对象的构造,同时会对紧急号码做一些处理。populateCacheEntry()关键代码如下:
public static void populateCacheEntry(Context context, CallerInfo info, ContactCacheEntry cce, int presentation, boolean isIncoming) { Preconditions.checkNotNull(info); String displayName = null; String displayNumber = null; String displayLocation = null; String label = null; boolean isSipCall = false; String number = info.phoneNumber; if (!TextUtils.isEmpty(number)) { isSipCall = PhoneNumberUtils.isUriNumber(number); if (number.startsWith("sip:")) { number = number.substring(4); } } // 如果CallerInfo的name为空则执行 // 通过前面的分析可以知道,CallerInfo的name默认赋值为cnapName,而 // cnapName并不是每个运营商都会支持。因此大多数情况下返回为空 if (TextUtils.isEmpty(info.name)) { if (TextUtils.isEmpty(number)) { // 如果CallerInfo的number也为空则表明当前通话为特殊通话 // 特殊通话需要显示Unknown PayPhone Private等特殊字段 displayName = getPresentationString(context, presentation); } else if (presentation != Call.PRESENTATION_ALLOWED) { // This case should never happen since the network should never send a phone # // AND a restricted presentation. However we leave it here in case of weird // network behavior displayName = getPresentationString(context, presentation); } else if (!TextUtils.isEmpty(info.cnapName)) { // 如果cnapName不为空,则将displayName设置未cnapName displayName = info.cnapName; info.name = info.cnapName; displayNumber = number; } else { // 如果当前通话的号码并未存储到用户的联系人列表中,将displayNumber设置为 // 对应的号码,后面显示的时候会判断,如果displayName为空的话,就显示displayNumber displayNumber = number; if (isIncoming) { // 如果是来电,则显示号码归属地相关信息 displayLocation = info.geoDescription; // may be null } } } else { // 如果info.name不为空,则表示之前的cnapName赋值成功,则将结果直接显示 if (presentation != Call.PRESENTATION_ALLOWED) { displayName = getPresentationString(context, presentation); } else { displayName = info.name; displayNumber = number; label = info.phoneLabel; } } // 最后将显示结果存放到ContactCacheEntry对象中 cce.name = displayName; cce.number = displayNumber; cce.location = displayLocation; cce.label = label; cce.isSipCall = isSipCall; }
通过以上方法将ContactCacheEntry对象构造完成之后,InCallActivity显示界面所需要的内容已经准备好,此时会调用sendInfoNotifications()发起回调通知,关键代码如下:
ContactInfoCache.java (\packages\apps\incallui\src\com\android\incallui)
/** * Sends the updated information to call the callbacks for the entry. */ private void sendInfoNotifications(String callId, ContactCacheEntry entry) { final Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId); Log.d(TAG, "onContactInfoComplete sendInfoNotifications()..."); if (callBacks != null) { for (ContactInfoCacheCallback callBack : callBacks) {// 回调所有的onContactInfoComplete方法 callBack.onContactInfoComplete(callId, entry); } } }
CallCardPresenter.java (\packages\apps\incallui\src\com\android\incallui)
/** * Starts a query for more contact data for the save primary and secondary calls. *///在CallCardPresenter的startContactInfoSearch方法里,发起联系人查询时 //在new ContactInfoCacheCallback()中匿名实现了onContactInfoComplete() private void startContactInfoSearch(final Call call, CallEnum type, boolean isIncoming) { final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);//在ContactInfoCache的findInfo方法中添加ContactInfoCacheCallback cache.findInfo(call, isIncoming, new ContactLookupCallback(this, type)); }
当数据库查询结束后,最终会通过CallCardPresenter.updateContactEntry()方法来更新界面,关键代码如下:
/** * Update the contact entry and view with specified view type. * * @param entry * @param type Includes the following three types: PRIMARY/SECONDARY/THIRD. * @param isConference */// 显示的信息是从ContactCacheEntry 获取的 private void updateContactEntry(ContactCacheEntry entry, CallEnum type, boolean isConference) { switch (type) { case PRIMARY: mPrimaryContactInfo = entry; updatePrimaryDisplayInfo(); break; case SECONDARY: mSecondaryContactInfo = entry; updateSecondaryDisplayInfo(); break; case THIRD: mThirdContactInfo = entry; updateThirdDisplayInfo(isConference); break; default: break; } }
三、界面显示Unknown的原因
在前面的分析中已经提到,在特殊情况下会显示特殊的displayName:
displayName = getPresentationString(context, presentation);private static String getPresentationString(Context context, int presentation) { String name = context.getString(R.string.unknown);//Unknown 位置号码 if (presentation == Call.PRESENTATION_RESTRICTED) { name = context.getString(R.string.private_num);// Private 私人号码 } else if (presentation == Call.PRESENTATION_PAYPHONE) { name = context.getString(R.string.payphone); //Pay Phone 共用电话 } return name;}
这里所说的特殊情况一般指的是运营商提供的一些服务,比如COLP 即Connected Line identification Presentation。该服务国内运营商称为——号码隐藏服务,即当用户开通该业务后,网络侧返回数据中不会包含该用户的号码信息。该服务目前国内运营商均已不再受理,以前办理过该业务的号码持续有效。 比如一名用户开启了该服务,呼叫该用户,当该用户接通来电后,主叫设备上不会显示对方的号码或者联系人信息,取而代之的是Unknown( 未知号码 )。如果遇到这种情况,可以通过查看相应的AT日志以及Modem日志来分析(注:MTK使用使用的AT Command,QCom使用的ShareMemory与Modem通信),如图4:
总结
关于InCallUI中displayName的获取需要注意以下三点:
1. 发起点在CallCardPresenter的init方法中,通过startContactInfoSearch()方法开始查询;
2. 查询过程主要分为四步:
①. CallerInfo获取
在CallerInfoUtils.getCallerInfoForCall()方法中获取CallerInfo对象。
②. 联系人数据库查询
在CallerInfoUtils.getCallerInfoForCall()方法中调用CallerInfoAsyncQuery.startQueryEx开启联系人数据库查询。注意:因为MTK在原生AOSP的基础上修改了代码,用以支持双SIM卡,因此有些地方与原生AOSP有些许不同。这里MTK的代码会执行frameworks/base/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java中的startQueryEx,而原生AOSP的代码则会执行packages/app/InCallUI/src/com/android/incallui/CallerInfoAsyncQuery.java中的startQuery。
③. 将查询结果返回
联系人数据库查询完毕之后需要将查询结果返回,并最终回调到ContactInfoCache.FindInfoCallback的onQueryComplete()方法中。
④. 显示displayName
最后通过ContactInfoCache.sendInfoNotifications()方式回调到CallCardPresenter中,并更新界面displayName。
3. 界面显示Unknown的原因,是因为号码为特殊号码,displayName的特殊号码包括:Unknown( 未知号码 )、Private( 私人号码 )、Pay Phone( 共用电话 )。具体原因则有可能是网络返回异常或运营商特殊服务(COLP/CNAP)等。
整个displayName获取并显示流程如图6所示:
- Android 6.0 Phone"通话显示"查询流程
- Android 4.4 Kitkat Phone工作流程浅析(十)__"通话显示"查询流程
- Android 4.4 Kitkat Phone工作流程浅析(十)__"通话显示"查询流程
- Android 6.0 Phone 多方通话流程
- Phone 通话过程中 PSensor 工作流程
- Android之通话流程
- android通话处理流程
- Android 通话处理流程
- android通话处理流程
- Android之监听phone的通话状态
- Android 通话处理流程【转】
- android 6.0-高通视频通话拨打流程
- Android5.0 InCall 通话界面显示流程
- android phone模块流程
- android通话流程浅析RIL层
- Android 4.4 Kitkat Phone工作流程浅析(六)__InCallActivity显示更新流程
- Android 4.4 Kitkat Phone工作流程浅析(六)__InCallActivity显示更新流程
- Android 4.4 Kitkat Phone工作流程浅析(六)__InCallActivity显示更新流程
- A Short introduction to descriptors,附带SIFT描述子的基本原理
- LightOJ1038---Race to 1 Again (概率dp(基础))
- spring data 接口之 JpaRepository,JpaSpecificationExecutor
- VC控制台内存泄露检查机制
- 微信小程序(false问题)
- Android 6.0 Phone"通话显示"查询流程
- Swift storyBoard 判断 segue 目标
- 用两个栈实现队列(java版)
- ADRC--笔记
- 如何将MyBatis或者iBatis的SQL打印到控制台?
- 使用Java实现简单的队列(queue)
- Maven的使用教程
- IntelliJ IDEA 类代码模板注解自定义
- 卷积神经网络模型(CNN)