I. Summary
Whether it's in the MT (Mobile Termination Call is called - Call) or MO (Mobile Origination Call is called - Call) process, the name of the current call will be displayed on the call interface (later referred to as displayName). Usually, if it is a strange number, it will be displayed as the strange number. If a contact is known, the name of the contact is displayed. Of course, in the case of conference calls, "conference calls" are displayed directly. However, in some special cases, displayName also displays "private number", "public phone", "unknown number" and so on.
This paper mainly analyses the process of displayName acquisition and the reason of displaying unknown number.
II. Query process
1. Start the query - CallCardPresenter
The displayName is a control that belongs to CallCard Fragment. InCallActivity will display when the call MO/MT process is initiated. This will trigger the CallCard Fragment interface update. The displayName is queried in the init method of CallCard Presenter. The key code is as follows:
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);
/// @}
}
}
}
The code for startContactInfoSearch is as follows:
/**
* 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);
// Start searching in ContactInfoCache
cache.findInfo(call, isIncoming, new ContactLookupCallback(this, type));
}
2. Asynchronous Query-ContactInfoCache
When a query is initiated in CallCardPresenter, it jumps to the ContactInfoCache.findInfo() method. ContactInfoCache is not only used to query the relevant information of the current call, but also to cache the information for quick return of the same information in the next query. FindInfo's key code is as follows:
/**
* 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) {
// Query the caller information, then call back to FindInfoCallback, and call findInfoQueryComplete
final CallerInfo callerInfo = CallerInfoUtils.getCallerInfoForCall(
mContext, call, new FindInfoCallback(isIncoming));
// When the query is completed, call back and update ContactEntry, which will eventually update the interface display.
findInfoQueryComplete(call, callerInfo, isIncoming, false);
}
CallerInfo contains the basic information of the current call, such as number, type, special related services, etc. After obtaining these information, further contact database queries are made.
3. Getting CallerInfo -- CallerInfoUtils
In the getCallerInfoForCall() method, besides obtaining the basic information of the current Call, it also queries the database according to the phoneNumber of the current Call. The key codes are as follows:
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) {
// Get the basic information of the current Call and create the CallerInfo object
CallerInfo info = buildCallerInfo(context, call);
// Open specific queries in CallerInfoAsyncQuery based on phoneNumber
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;
}
In the above code, there are two important methods, namely buildCallerInfo() and CallerInfoAsyncQuery.startQuery(), first query the key code of 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).
// Getting the CurrentCallOf CNAP name
info.cnapName = call.getCnapName();
info.name = info.cnapName;
info.numberPresentation = call.getNumberPresentation();
info.namePresentation = call.getCnapNamePresentation();
String number = call.getNumber();
// Getting the CurrentCallOf number,If not empty, execute
if (!TextUtils.isEmpty(number)) {
final String[] numbers = number.split("&");
number = numbers[0];
if (numbers.length > 1) {
info.forwardingNumber = numbers[1];
}
// number Display with Special Processing for CNAP
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;
}
If the callee of the current Call does not open the service, the value of cnapName returns to null. At the same time, in the method of buildCallerInfo, whether the number of the current Call is empty is also judged. Number here comes from the return of the network side, for example, as the calling party, when the call is connected, the number of the called party will be returned through the network, and in some special cases the return value may be empty.
Note: About CNAP
CNAP, short for Calling Name Presentation, is a service provided by operators. For example, after the user opens the service, set Calling Name Presentation as "HelloSeven" at the operator. When the user talks to other users, displayName will be displayed as "Hello Seven" regardless of whether the number is stored in the other user's contacts, if the other user's mobile phone supports CNAP function. Some operators in Canada use the service, such as Rogers, but at present domestic operators do not support the service.
4. Query database-CallerInfoAsyncQuery
When buildCallerInfo() is completed, the local Contacts database is queried based on the number of the current Call. Take MTK dual card as an example, so CallerInfoAsyncQuery.startQueryEx(QUERY_TOKEN, context, number,listener, call, call.getSlotId()) method is executed. The key code is as follows (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);//Note here that allocate assigns the mHanlder object
//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.
// Setting query types include: 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;
}
// Start the query
c.mHandler.startQuery(token,
cw, // cookie
contactRef, // uri
null, // projection
null, // selection
null, // selectionArgs
null); // orderBy
return c;
}
The above code mainly completes: setting the database table corresponding to the query; setting the type of query (emergency number, voice number, common query); initiating database query. The c.allocate() method assigns mHandler. The key code is as follows:
/**
* 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;
}
When c.mHandler.startQuery is executed, the startQuery method in CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler is queried first, and then the startQuery method in the parent AsyncQueryHandler (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);
//Type EVENT_ARG_QUERY
msg.arg1 = EVENT_ARG_QUERY;
WorkerArgs args = new WorkerArgs();
//Jump from CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler to AsyncQueryHandler, so this is the CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler object
args.handler = this;
args.uri = uri;
args.projection = projection;
args.selection = selection;
args.selectionArgs = selectionArgs;
args.orderBy = orderBy;
args.cookie = cookie;
msg.obj = args;
//Here's an example of mWorkerThreadHandler in
mWorkerThreadHandler.sendMessage(msg);
}
Finally, the mWorkerThreadHandler.sendMessage() method is used to jump to the handleMessage method of CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler.CallerInfoWorkerHandler. The key code is as follows:
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;
//The cw here is set in startQueryEx, not 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) {
//At this point event is 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:
}
}
}
}
If it's just a normal number query, execute case EVENT_NEW_QUERY and call back to the handleMessage method of the parent AsyncQueryHandler.WorkerHandler:
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 {
// Query the Phone Lookup table in Contacts database
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;
}
// Save the query results to 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.
//Note: The args.handler object here is actually an instance of 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();
}
}
After querying in WorkHandler, execute args.handler.obtainMessage(), where args.handler is actually an instance of CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler, but there is no handleMessage method in CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler, so call back the handleMessage method of its parent class AsyncQueryHandler:
@Override
public void handleMessage(Message msg) {
// pass token back to caller on each callback.
switch (event) {
// Execute after the query is completed
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;
}
}
The onQueryComplete method can be regarded as this.onQueryComplete, and this comes from CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler. Therefore, we will call back to the onQueryComplete method of CallerInfoAsyncQuery.CallerInfoAsyncQueryHandler. The key code is as follows:
@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.
//When the query is finished, the query result will be returned.
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();
}
}
}
}
The cw.listener in the above code is from CallerInfoAsyncQuery.startQueryEx(), and as you can see in ContactInfoCache.findInfo(), listener is actually the object of 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);
}
}
That is to say, when the query is completed, it calls back to the onQueryComplete() method of ContactInfoCache.FindInfoCallback and executes ContactInfoCache.findInfoQueryComplete().
5. Improving the Query Result-ContactInfoCache
After various callbacks, the query results are finally returned to the ContactInfoCache.findInfoQueryComplete method, which is mainly used to encapsulate the query results as ContactCacheEntry objects and initiate the callback after the query. The key code is as follows:
private void findInfoQueryComplete(CallIdentification identification,
CallerInfo callerInfo, boolean isIncoming, boolean didLocalLookup) {
//Save the query results to ContactCacheEntry
final ContactCacheEntry cacheEntry = buildEntry(mContext, callId,
callerInfo, presentationMode, isIncoming);
//Correspond cacheEntry to the corresponding callId
mInfoMap.put(callId, cacheEntry);
if(!mExpiredInfoMap.containsKey(callId)) {
//Notify the completed query message to the corresponding callback method
sendInfoNotifications(callId, cacheEntry);
}
//... ...ellipsis
}
Note the following two points:
BuilEntry () prepares the final display for subsequent use.
sendInfoNotifications() initiates callbacks and notifies relevant listener s that the query is complete and available for display;
The key code for looking at buildEntry() is as follows:
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;
//Constructing 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;
}
In the method of buildEntry, we call populateCacheEntry() to complete the construction of ContactCacheEntry object, and do some processing for emergency number. The key code for populateCacheEntry() is as follows:
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);
}
}
// Execute if CallerInfo's name is empty
// As you can see from the previous analysis, CallerInfo's name is assigned to cnapName by default, while CallerInfo's name is assigned to cnapName by default.
// cnapName is not supported by every operator. So in most cases, the return is empty.
if (TextUtils.isEmpty(info.name)) {
if (TextUtils.isEmpty(number)) {
// If the number of CallerInfo is also empty, it indicates that the current call is a special one.
// Special calls need to display special fields such as 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)) {
// If cnapName is not empty, set displayName to cnapName
displayName = info.cnapName;
info.name = info.cnapName;
displayNumber = number;
} else {
// If the number of the current call is not stored in the user's contact list, set displayNumber to
// If displayName is empty, displayNumber will be displayed.
displayNumber = number;
if (isIncoming) {
// If it's a call, display the information about the location of the number.
displayLocation = info.geoDescription; // may be null
}
}
} else {
// If info.name is not empty, the result will be displayed directly if the previous cnapName assignment is successful
if (presentation != Call.PRESENTATION_ALLOWED) {
displayName = getPresentationString(context, presentation);
} else {
displayName = info.name;
displayNumber = number;
label = info.phoneLabel;
}
}
// Finally, the display results are stored in the ContactCacheEntry object
cce.name = displayName;
cce.number = displayNumber;
cce.location = displayLocation;
cce.label = label;
cce.isSipCall = isSipCall;
}
After the ContactCacheEntry object is constructed by the above method, the content required for the InCallActivity display interface is ready. sendInfoNotifications() is called to initiate callback notification. The key code is as follows:
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) {
// Callback all onContactInfoComplete methods
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.
*/
//In the startContactInfoSearch method of CallCardPresenter, when a contact query is initiated
//onContactInfoComplete() is anonymously implemented in new ContactInfoCacheCallback()
private void startContactInfoSearch(final Call call, CallEnum type,
boolean isIncoming) {
final ContactInfoCache cache = ContactInfoCache.getInstance(mContext);
//Add ContactInfoCacheCallback to ContactInfoCache's findInfo method
cache.findInfo(call, isIncoming, new ContactLookupCallback(this, type));
}
When the database query is finished, the interface will be updated by CallCardPresenter.updateContactEntry(), the key code is as follows:
/**
* Update the contact entry and view with specified view type.
*
* @param entry
* @param type Includes the following three types: PRIMARY/SECONDARY/THIRD.
* @param isConference
*/
// The information displayed is retrieved from 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;
}
}
3. Reasons for Unknowown Display in Interface
As mentioned in the previous analysis, special displayName will be displayed in special cases:
displayName = getPresentationString(context, presentation);
private static String getPresentationString(Context context, int presentation) {
String name = context.getString(R.string.unknown);//Unknown Location Number
if (presentation == Call.PRESENTATION_RESTRICTED) {
name = context.getString(R.string.private_num);// Private private number
} else if (presentation == Call.PRESENTATION_PAYPHONE) {
name = context.getString(R.string.payphone); //Pay Phone Common Phone
}
return name;
}
The special cases mentioned here generally refer to some services provided by operators, such as COLP, or Connected Line identification Presentation. This service is called number hiding service by domestic operators, that is to say, when the user opens the service, the return data on the network side will not contain the user's number information. At present, the service is no longer accepted by domestic operators, and the number that has handled the service before is still valid. For example, when a user opens the service and calls the user, the calling device will not display the other party's number or contact information, but instead Unknown (unknown number). If this happens, you can analyze it by looking at the corresponding AT log and Modem log (Note: MTK uses AT Command, QCom uses ShareMemory to communicate with Modem), as shown in Figure 4:
summary
The following three points should be paid attention to when acquiring displayName in InCallUI:
1. In the init method of CallCardPresenter, the initiation point starts the query by the startContactInfoSearch() method.
2. The query process is divided into four steps:
(1) CallerInfo acquisition
Get the CallerInfo object in the CallerInfoUtils.getCallerInfoForCall() method.
(2) Contact database query
CallerInfoAsyncQuery.startQueryEx is called in the CallerInfoUtils.getCallerInfoForCall() method to open the contact database query. Note: Because MTK modifies the code based on native AOSP to support dual SIM cards, there are some differences between MTK and native AOSP. Here MTK code executes startQueryEx in frameworks/base/telephony/java/com/android/internal/telephony/CallerInfoAsyncQuery.java, while native AOSP code executes startQuery in packages/app/InCall/src/com/android/incallui/CallerInfoAsyncQuery.java.
(3) Return the query results
After the contact database query is completed, the query result needs to be returned and finally callback to the onQueryComplete() method of ContactInfoCache.FindInfoCallback.
Display Name
Finally, call back to CallCardPresenter by ContactInfoCache.sendInfoNotifications() and update the interface displayName.
3. The reason why the interface shows Unknown is that the number is a special number. The special numbers of displayName include Unknown, Private and Pay Phone. Specific reasons may be network return anomaly or operator special services (COLP/CNAP).
The entire displayName acquisition and display process is shown in Figure 6.