package com.android.incallui; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.text.TextUtils; import com.android.services.telephony.common.Call; import com.android.services.telephony.common.CallIdentification; import java.util.Arrays; /** * Utility methods for contact and caller info related functionality */ public class CallerInfoUtils { private static final String TAG = CallerInfoUtils.class.getSimpleName(); /** Define for not a special CNAP string */ private static final int CNAP_SPECIAL_CASE_NO = -1; private static final String VIEW_NOTIFICATION_ACTION = "com.android.contacts.VIEW_NOTIFICATION"; private static final String VIEW_NOTIFICATION_PACKAGE = "com.android.contacts"; private static final String VIEW_NOTIFICATION_CLASS = "com.android.contacts.ViewNotificationService"; public CallerInfoUtils() { } private static final int QUERY_TOKEN = -1; /** * 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, CallIdentification call, CallerInfoAsyncQuery.OnQueryCompleteListener listener) { CallerInfo info = buildCallerInfo(context, call); String number = info.phoneNumber; // TODO: Have phoneapp send a Uri when it knows the contact that triggered this call. if (info.numberPresentation == Call.PRESENTATION_ALLOWED) { // Start the query with the number provided from the call. Log.d(TAG, "==> Actually starting CallerInfoAsyncQuery.startQuery()..."); CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, number, listener, call); } return info; } public static CallerInfo buildCallerInfo(Context context, CallIdentification identification) { 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). info.cnapName = identification.getCnapName(); info.name = info.cnapName; info.numberPresentation = identification.getNumberPresentation(); info.namePresentation = identification.getCnapNamePresentation(); String number = identification.getNumber(); if (!TextUtils.isEmpty(number)) { final String[] numbers = number.split("&"); number = numbers[0]; if (numbers.length > 1) { info.forwardingNumber = numbers[1]; } number = modifyForSpecialCnapCases(context, info, number, info.numberPresentation); info.phoneNumber = number; } return info; } /** * Handles certain "corner cases" for CNAP. When we receive weird phone numbers * from the network to indicate different number presentations, convert them to * expected number and presentation values within the CallerInfo object. * @param number number we use to verify if we are in a corner case * @param presentation presentation value used to verify if we are in a corner case * @return the new String that should be used for the phone number */ /* package */static String modifyForSpecialCnapCases(Context context, CallerInfo ci, String number, int presentation) { // Obviously we return number if ci == null, but still return number if // number == null, because in these cases the correct string will still be // displayed/logged after this function returns based on the presentation value. if (ci == null || number == null) return number; Log.d(TAG, "modifyForSpecialCnapCases: initially, number=" + toLogSafePhoneNumber(number) + ", presentation=" + presentation + " ci " + ci); // "ABSENT NUMBER" is a possible value we could get from the network as the // phone number, so if this happens, change it to "Unknown" in the CallerInfo // and fix the presentation to be the same. final String[] absentNumberValues = context.getResources().getStringArray(R.array.absent_num); if (Arrays.asList(absentNumberValues).contains(number) && presentation == Call.PRESENTATION_ALLOWED) { number = context.getString(R.string.unknown); ci.numberPresentation = Call.PRESENTATION_UNKNOWN; } // Check for other special "corner cases" for CNAP and fix them similarly. Corner // cases only apply if we received an allowed presentation from the network, so check // if we think we have an allowed presentation, or if the CallerInfo presentation doesn't // match the presentation passed in for verification (meaning we changed it previously // because it's a corner case and we're being called from a different entry point). if (ci.numberPresentation == Call.PRESENTATION_ALLOWED || (ci.numberPresentation != presentation && presentation == Call.PRESENTATION_ALLOWED)) { int cnapSpecialCase = checkCnapSpecialCases(number); if (cnapSpecialCase != CNAP_SPECIAL_CASE_NO) { // For all special strings, change number & numberPresentation. if (cnapSpecialCase == Call.PRESENTATION_RESTRICTED) { number = context.getString(R.string.private_num); } else if (cnapSpecialCase == Call.PRESENTATION_UNKNOWN) { number = context.getString(R.string.unknown); } Log.d(TAG, "SpecialCnap: number=" + toLogSafePhoneNumber(number) + "; presentation now=" + cnapSpecialCase); ci.numberPresentation = cnapSpecialCase; } } Log.d(TAG, "modifyForSpecialCnapCases: returning number string=" + toLogSafePhoneNumber(number)); return number; } /** * Based on the input CNAP number string, * @return _RESTRICTED or _UNKNOWN for all the special CNAP strings. * Otherwise, return CNAP_SPECIAL_CASE_NO. */ private static int checkCnapSpecialCases(String n) { if (n.equals("PRIVATE") || n.equals("P") || n.equals("RES")) { Log.d(TAG, "checkCnapSpecialCases, PRIVATE string: " + n); return Call.PRESENTATION_RESTRICTED; } else if (n.equals("UNAVAILABLE") || n.equals("UNKNOWN") || n.equals("UNA") || n.equals("U")) { Log.d(TAG, "checkCnapSpecialCases, UNKNOWN string: " + n); return Call.PRESENTATION_UNKNOWN; } else { Log.d(TAG, "checkCnapSpecialCases, normal str. number: " + n); return CNAP_SPECIAL_CASE_NO; } } /* package */static String toLogSafePhoneNumber(String number) { // For unknown number, log empty string. if (number == null) { return ""; } // Todo: Figure out an equivalent for VDBG if (false) { // When VDBG is true we emit PII. return number; } // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare // sanitized phone numbers. StringBuilder builder = new StringBuilder(); for (int i = 0; i < number.length(); i++) { char c = number.charAt(i); if (c == '-' || c == '@' || c == '.' || c == '&') { builder.append(c); } else { builder.append('x'); } } return builder.toString(); } /** * Send a notification that that we are viewing a particular contact, so that the high-res * photo is downloaded by the sync adapter. */ public static void sendViewNotification(Context context, Uri contactUri) { final Intent intent = new Intent(VIEW_NOTIFICATION_ACTION, contactUri); intent.setClassName(VIEW_NOTIFICATION_PACKAGE, VIEW_NOTIFICATION_CLASS); context.startService(intent); } }