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所示:

原创粉丝点击