关闭

Android 4.4 Kitkat Phone工作流程浅析(十)__"通话显示"查询流程

标签: TelephonydisplayNameKitkatPhone
6092人阅读 评论(1) 收藏 举报
分类:

本文来自http://blog.csdn.net/yihongyuelan 转载请务必注明出处

本文代码以MTK平台Android 4.4为分析对象,与Google原生AOSP有些许差异,请读者知悉。

前置文章:

Android 4.4 Kitkat Phone工作流程浅析(一)__概要和学习计划

Android 4.4 Kitkat Phone工作流程浅析(二)__UI结构分析

Android 4.4 Kitkat Phone工作流程浅析(三)__MO(去电)流程分析

Android 4.4 Kitkat Phone工作流程浅析(四)__RILJ工作流程简析

Android 4.4 Kitkat Phone工作流程浅析(五)__MT(来电)流程分析

Android 4.4 Kitkat Phone工作流程浅析(六)__InCallActivity显示更新流程

Android 4.4 Kitkat Phone工作流程浅析(七)__来电(MT)响铃流程

Android 4.4 Kitkat Phone工作流程浅析(八)__Phone状态分析

Android 4.4 Kitkat Phone工作流程浅析(九)__状态通知流程分析

概要

      无论是在MT (Mobile Termination Call被叫——来电),还是MO (Mobile Origination Call主叫——去电) 流程中,通话界面上都会显示当前通话的名称( 后文以displayName指代 )。通常情况下,如果是一个陌生号码,则会显示为该陌生号码。如果是已知联系人,则会显示该联系人的名称。当然,在会议电话( Conference Call )的情况下则直接显示"会议电话"。但是,在某些特殊情况下,displayName还会显示诸如"私人号码"、"公用电话"、"未知号码"等。

      本文主要分析displayName的获取显示流程及显示"未知号码"的原因,如图1:


图 1 通话界面显示Unknown

查询流程

开始查询——CallCardPresenter

      displayName是隶属于CallCardFragment的控件,当通话MO/MT流程发起时InCallActivity会显示,此时将会触发CallCardFragment界面更新,在CallCardPresenter的init方法中查询displayName,关键代码如下:

public void init(Context context, Call call) {
    //... ...省略
    if (call != null) {
        mPrimary = call;
        final CallIdentification identification = call.getIdentification();
        // start processing lookups right away.
        if (!call.isConferenceCall()) {
            //... ...如果不是ConferenceCall就执行
            startContactInfoSearch(identification, PRIMARY,
                    call.getState() == Call.State.INCOMING);
        } else {
            //... ..如果是ConferenceCall则执行
            updateContactEntry(null, PRIMARY, true);
        }
    }
}
startContactInfoSearch的具体代码如下:
private void startContactInfoSearch(final CallIdentification identification,
        final boolean isPrimary, boolean isIncoming) {
        final int type, boolean isIncoming) {
    final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);
    // ContactInfoCache中开始查找
    cache.findInfo(identification, isIncoming, new ContactInfoCacheCallback() {
            @Override
            public void onContactInfoComplete(int callId, ContactCacheEntry entry) {
                // 当查询完毕之后回调并更新ContactEntry,这里最终会去更新界面显示
                updateContactEntry(entry, type, false);
                if (entry.name != null) {
                    Log.d(TAG, "Contact found: " + entry);
                }
                if (entry.personUri != null) {
                    CallerInfoUtils.sendViewNotification(mContext, entry.personUri);
                }
            }

            @Override
            public void onImageLoadComplete(int callId, ContactCacheEntry entry) {
                if (getUi() == null) {
                    return;
                }
                if (entry.photo != null) {
                    if (mPrimary != null && callId == mPrimary.getCallId()) {
                        // 设置第一路通话头像
                        getUi().setPrimaryImage(entry.photo);
                    } else if (mSecondary != null && callId == mSecondary.getCallId()) {
                        // 设置第二路通话头像
                        getUi().setSecondaryImage(entry.photo);
                    }
                }
            }
        });
}

异步查询——ContactInfoCache

      在CallCardPresenter中发起查询之后会跳转到ContactInfoCache.findInfo()方法中,ContactInfoCache不仅用于查询当前通话的相关信息,还可以将这些信息缓存以备下次查询相同信息时快速返回。findInfo关键代码如下:

public void findInfo(final CallIdentification identification, final boolean isIncoming,
        ContactInfoCacheCallback callback) {
    //... ...省略
    // 查询caller信息,完成之后会回调到FindInfoCallback中,会调用findInfoQueryComplete
    final CallerInfo callerInfo = CallerInfoUtils.getCallerInfoForCall(
            mContext, identification, new FindInfoCallback(isIncoming));

    if(!mExpiredInfoMap.containsKey(callId)) {
        // 查询信息完成之后执行
        findInfoQueryComplete(identification, callerInfo, isIncoming, false);
    }
}

CallerInfo中包含了当前call的基本信息,比如号码、类型、特殊相关服务等,在获取到这些信息之后再进行进一步的联系人数据库查询。

获取CallerInfo——CallerInfoUtils

      在getCallerInfoForCall()方法中,除了获取当前Call的基本信息之外,还会根据当前Call的phoneNumber去数据库中查询,关键代码如下:
public static CallerInfo getCallerInfoForCall(Context context, CallIdentification call,
        CallerInfoAsyncQuery.OnQueryCompleteListener listener) {
    // 获取当前Call的基本信息并创建CallerInfo对象
    CallerInfo info = buildCallerInfo(context, call);
    String number = info.phoneNumber;

    // 根据phoneNumber在CallerInfoAsyncQuery中开启具体查询
    // MTK支持双SIM卡,因此这里做了一些修改
    if (info.numberPresentation == Call.PRESENTATION_ALLOWED) {
        // Start the query with the number provided from the call.
        Call c = CallList.getInstance().getCall(call.getCallId());
        boolean isSipPhone = (c == null ? false : (c.getPhoneType() == PhoneConstants.PHONE_TYPE_SIP));
        if (GeminiConstants.SOLT_NUM >= 2 && !isSipPhone) {
            CallerInfoAsyncQuery.startQueryEx(QUERY_TOKEN, context, number,
                    listener, call, call.getSlotId());
        } else if (isSipPhone) {
            CallerInfoAsyncQuery.startQueryEx(QUERY_TOKEN, context, number, listener, call, -1);
        } else {
            CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, number,
                    listener, call);
        }
    }
    return info;
}
在以上代码中,有两个重要的方法,即buildCallerInfo()和CallerInfoAsyncQuery.startQuery(),先查询看buildCallerInfo()的关键代码:
public static CallerInfo buildCallerInfo(Context context, CallIdentification identification) {
    CallerInfo info = new CallerInfo();

    // 获取当前Call的CNAP name
    info.cnapName = identification.getCnapName();
    info.name = info.cnapName;
    info.numberPresentation = identification.getNumberPresentation();
    info.namePresentation = identification.getCnapNamePresentation();

    String number = identification.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;
    }
    return info;
}

      如果当前Call的被叫一方没有开通该业务,则cnapName的值返回为空。同时,在buildCallerInfo方法中也对当前Call的number是否为空做了判断。这里的number来自于网络侧的返回,比如作为主叫方,当通话接通后被叫方的号码会通过网络返回,在某些特殊的情况下返回值有可能为空。

      关于CNAP

      CNAP即Calling Name Presentation的缩写,是运营商提供的一种服务。比如,用户开通该服务后,在运营商处设置Calling Name Presentation为"HelloSeven"。当该用户与其他用户通话时,如果对方的手机支持CNAP功能,那么无论对方联系人里是否存入了该号码,displayName都会显示为"HelloSeven"。加拿大的一些运营商有使用该服务,比如Rogers,但目前国内的运营商均不支持该服务。

      As the standard says: “Calling Name Presentation (CNAP) provides the name identification of the calling party (e.g., personal name, company name, “restricted”, “not available”) to the called subscriber”. This means in practice that when somebody calls you your phone will receive not just the number of the callee but also her name information if such is available. More specifically if somebody calls you, you can see her name even if she’s number is not in the contact list stored in your mobile device.

      参考1  参考2       

查询数据库——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 ):

public static CallerInfoAsyncQuery startQueryEx(int token, Context context, String number,
        OnQueryCompleteListener listener, Object cookie, int simId) {
    //... ...省略
    // 设置SIP Phone的查询条件
    if (PhoneNumberUtils.isUriNumber(number)) {
        // "number" is really a SIP address.
        contactRef = Data.CONTENT_URI;
        selection = "upper(" + Data.DATA1 + ")=?"
                + " AND "
                + Data.MIMETYPE + "='" + SipAddress.CONTENT_ITEM_TYPE + "'";
        selectionArgs = new String[] { number.toUpperCase() };
    } else {
        // 如果是普通号码,则在PhoneLookup表中查询
        contactRef = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
        selection = null;
        selectionArgs = null;
    }

    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 = number;
    cw.simId = simId;
    
    // ... ...省略
    boolean isECCNumber;        
    if (FeatureOption.MTK_GEMINI_SUPPORT) {
       int phoneType = PHONE_TYPE_GSM;
       try {
             ITelephony iTel = ITelephony.Stub.asInterface(ServiceManager.getService(Context.TELEPHONY_SERVICE));
             phoneType = iTel.getActivePhoneTypeGemini(simId);
       } catch (Exception e) {
       }
       isECCNumber = PhoneNumberUtils.isEmergencyNumberExt(number, phoneType);
    }else {
      isECCNumber = PhoneNumberUtils.isEmergencyNumber(number);
    }
    // 设置查询类型包括:EMERGENCY_NUMBER、VOICEMAIL、NEW_QUERY
    if (isECCNumber) {
        cw.event = EVENT_EMERGENCY_NUMBER;
    } else if ((originalSimId != -1) && (mPhoneNumberExt.isVoiceMailNumber(number, simId))) {
        cw.event = EVENT_VOICEMAIL_NUMBER;
    } else {
        cw.event = EVENT_NEW_QUERY;
    }
    // 开始查询
    c.mHandler.startQuery(token,
                          cw,  // cookie
                          contactRef,  // uri
                          null,  // projection
                          selection,  // selection
                          selectionArgs,  // selectionArgs
                          null);  // orderBy
    return c;
}

      以上代码中主要完成:设置查询对应的数据库表;设置查询类型(紧急号码、语音号码、普通查询);发起数据库查询。其中c.allocate()方法会对mHandler进行赋值,关键代码如下:

private void allocate(Context context, Uri contactRef) {
    //... ...省略
    //其中CallerInfoAsyncQueryHandler extends AsyncQueryHandler,是CallerInfoAsyncQuery的内部类
    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 ):
public void startQuery(int token, Object cookie, Uri uri,
        String[] projection, String selection, String[] selectionArgs,
        String orderBy) {
    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);
}

以上代码有几个需要注意的地方:

①. args.handler = this

因为代码是从CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler调用到AsyncQueryHandler.startQuery方法的,所以这里的this对象是CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler而不是AsyncQueryHandler。

②. mWorkThreadHandler实际上是CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler的对象

通过图2和图3可以看到mWorkThreadHandler的实例化流程:


图 2 CallerInfoAsyncQueryHandler以及AsyncQueryHandler关系类图


图 3 mWorkThreadHandler实例化流程

      最后通过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) {
            super.handleMessage(msg);
        } else {
            switch (cw.event) {
                //此时event为NEW_QUERY
                case EVENT_NEW_QUERY:
                    //直接回调父类中的handleMessage方法
                    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方法中:
protected class WorkerHandler extends Handler {
    //... ...省略
    @Override
    public void handleMessage(Message msg) {
        //... ...省略
        switch (event) {
            case EVENT_ARG_QUERY:
                Cursor cursor;
                try {
                    // 查询Contacts数据库中的PhoneLookup表
                    cursorcursor = 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;
        }
        //注意:这里的args.handler对象实际上是CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler的实例
        Message reply = args.handler.obtainMessage(token);
        reply.obj = args;
        reply.arg1 = msg.arg1;
        reply.sendToTarget();
    }
}

      在WorkHandler中查询完毕之后,执行args.handler.obtainMessage(),这里的args.handler实际上是CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler的实例,但在CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler中并没有handleMessage方法,因此回调其父类AsyncQueryHandler的handleMessage方法:

@Override
public void handleMessage(Message msg) {
    //... ...省略
    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) {
    CookieWrapper cw = (CookieWrapper) cookie;
    // ... ...省略
    //查询完毕,将查询结果返回.
    if (cw.listener != null) {
        cw.listener.onQueryComplete(token, cw.cookie, mCallerInfo);
    }
    // ... ...省略
}
      以上代码中的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()。

完善查询结果——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, int callId,
        CallerInfo info, int presentation, boolean isIncoming) {
    //... ...省略
    //构造ContatcCacheEntry
    populateCacheEntry(context, info, cce, presentation, isIncoming);
    //... ...省略
    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()发起回调通知,关键代码如下:
private void sendInfoNotifications(int callId, ContactCacheEntry entry) {
    final Set<ContactInfoCacheCallback> callBacks = mCallBacks.get(callId);
    if (callBacks != null) {
        for (ContactInfoCacheCallback callBack : callBacks) {
            // 回调所有的onContactInfoComplete方法
            callBack.onContactInfoComplete(callId, entry);
        }
    }
}

//在CallCardPresenter的startContactInfoSearch方法里,发起联系人查询时
//在new ContactInfoCacheCallback()中匿名实现了onContactInfoComplete()
private void startContactInfoSearch(final CallIdentification identification,
        final int type, boolean isIncoming) {
    cache.findInfo(identification, isIncoming, new ContactInfoCacheCallback() {
        @Override
        public void onContactInfoComplete(int callId, ContactCacheEntry entry) {
            // 更新界面
            updateContactEntry(entry, type, false);
            //... ...省略
        }
        @Override
        public void onImageLoadComplete(int callId, ContactCacheEntry entry) {
            //... ...省略
        }
    });
}

//在ContactInfoCache的findInfo方法中添加ContactInfoCacheCallback
public void findInfo(final CallIdentification identification, final boolean isIncoming,
        ContactInfoCacheCallback callback) {
    //... ...省略
    callBacks.add(callback);//添加callback
    mCallBacks.put(callId, callBacks);
    //... ...省略
}
      当数据库查询结束后,最终会通过CallCardPresenter.updateContactEntry()方法来更新界面,关键代码如下:
private void updateContactEntry(ContactCacheEntry entry, int type, boolean isConference) {
    if (type == PRIMARY) {
        mPrimaryContactInfo = entry;
        updatePrimaryDisplayInfo(entry, isConference);
    } else if (type == SECONDARY) {
        mSecondaryContactInfo = entry;
        updateSecondaryDisplayInfo(isConference);
    } else if (type == SECONDARY_ONHOLD) {
        updateSecondaryHoldCallDisplayInfo(isConference);
    } else if (type == SECONDARY_INCOMING) {
        updateSecondaryIncomingCallDisplayInfo(isConference);
    }
}

界面显示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:


图 4 被叫开启COLP服务,主叫MO AT返回

      关于AT +ECPI指令说明请参看《Android 4.4 Kitkat Phone工作流程浅析(六)__InCallActivity显示更新流程》。这里可以看到当被叫接通后,即+ECPI状态从2->132->6时,Modem侧没有将被叫的号码返回。同时,查看Modem日志信息也反映出被叫开启了COLP服务,如图5:


图 5 Modem返回日志

总结

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


图 6 displayName查询显示流程


本文中所需资源免积分下载:戳这里

3
0

猜你在找
深度学习基础与TensorFlow实践
【在线峰会】前端开发重点难点技术剖析与创新实践
【在线峰会】一天掌握物联网全栈开发之道
【在线峰会】如何高质高效的进行Android技术开发
机器学习40天精英计划
Python数据挖掘与分析速成班
微信小程序开发实战
JFinal极速开发企业实战
备战2017软考 系统集成项目管理工程师 学习套餐
Python大型网络爬虫项目开发实战(全套)
查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:761272次
    • 积分:7730
    • 等级:
    • 排名:第2519名
    • 原创:59篇
    • 转载:0篇
    • 译文:4篇
    • 评论:429条
    文章分类
    最新评论