1 package com.android.incallui; 2 3 import android.content.Context; 4 import android.content.Intent; 5 import android.net.Uri; 6 import android.text.TextUtils; 7 8 import com.android.services.telephony.common.Call; 9 import com.android.services.telephony.common.CallIdentification; 10 11 import java.util.Arrays; 12 13 /** 14 * Utility methods for contact and caller info related functionality 15 */ 16 public class CallerInfoUtils { 17 18 private static final String TAG = CallerInfoUtils.class.getSimpleName(); 19 20 /** Define for not a special CNAP string */ 21 private static final int CNAP_SPECIAL_CASE_NO = -1; 22 23 private static final String VIEW_NOTIFICATION_ACTION = 24 "com.android.contacts.VIEW_NOTIFICATION"; 25 private static final String VIEW_NOTIFICATION_PACKAGE = "com.android.contacts"; 26 private static final String VIEW_NOTIFICATION_CLASS = 27 "com.android.contacts.ViewNotificationService"; 28 29 public CallerInfoUtils() { 30 } 31 32 private static final int QUERY_TOKEN = -1; 33 34 /** 35 * This is called to get caller info for a call. This will return a CallerInfo 36 * object immediately based off information in the call, but 37 * more information is returned to the OnQueryCompleteListener (which contains 38 * information about the phone number label, user's name, etc). 39 */ 40 public static CallerInfo getCallerInfoForCall(Context context, CallIdentification call, 41 CallerInfoAsyncQuery.OnQueryCompleteListener listener) { 42 CallerInfo info = buildCallerInfo(context, call); 43 String number = info.phoneNumber; 44 45 // TODO: Have phoneapp send a Uri when it knows the contact that triggered this call. 46 47 if (info.numberPresentation == Call.PRESENTATION_ALLOWED) { 48 // Start the query with the number provided from the call. 49 Log.d(TAG, "==> Actually starting CallerInfoAsyncQuery.startQuery()..."); 50 CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context, number, listener, call); 51 } 52 return info; 53 } 54 55 public static CallerInfo buildCallerInfo(Context context, CallIdentification identification) { 56 CallerInfo info = new CallerInfo(); 57 58 // Store CNAP information retrieved from the Connection (we want to do this 59 // here regardless of whether the number is empty or not). 60 info.cnapName = identification.getCnapName(); 61 info.name = info.cnapName; 62 info.numberPresentation = identification.getNumberPresentation(); 63 info.namePresentation = identification.getCnapNamePresentation(); 64 65 String number = identification.getNumber(); 66 if (!TextUtils.isEmpty(number)) { 67 final String[] numbers = number.split("&"); 68 number = numbers[0]; 69 if (numbers.length > 1) { 70 info.forwardingNumber = numbers[1]; 71 } 72 73 number = modifyForSpecialCnapCases(context, info, number, info.numberPresentation); 74 info.phoneNumber = number; 75 } 76 return info; 77 } 78 79 /** 80 * Handles certain "corner cases" for CNAP. When we receive weird phone numbers 81 * from the network to indicate different number presentations, convert them to 82 * expected number and presentation values within the CallerInfo object. 83 * @param number number we use to verify if we are in a corner case 84 * @param presentation presentation value used to verify if we are in a corner case 85 * @return the new String that should be used for the phone number 86 */ 87 /* package */static String modifyForSpecialCnapCases(Context context, CallerInfo ci, 88 String number, int presentation) { 89 // Obviously we return number if ci == null, but still return number if 90 // number == null, because in these cases the correct string will still be 91 // displayed/logged after this function returns based on the presentation value. 92 if (ci == null || number == null) return number; 93 94 Log.d(TAG, "modifyForSpecialCnapCases: initially, number=" 95 + toLogSafePhoneNumber(number) 96 + ", presentation=" + presentation + " ci " + ci); 97 98 // "ABSENT NUMBER" is a possible value we could get from the network as the 99 // phone number, so if this happens, change it to "Unknown" in the CallerInfo 100 // and fix the presentation to be the same. 101 final String[] absentNumberValues = 102 context.getResources().getStringArray(R.array.absent_num); 103 if (Arrays.asList(absentNumberValues).contains(number) 104 && presentation == Call.PRESENTATION_ALLOWED) { 105 number = context.getString(R.string.unknown); 106 ci.numberPresentation = Call.PRESENTATION_UNKNOWN; 107 } 108 109 // Check for other special "corner cases" for CNAP and fix them similarly. Corner 110 // cases only apply if we received an allowed presentation from the network, so check 111 // if we think we have an allowed presentation, or if the CallerInfo presentation doesn't 112 // match the presentation passed in for verification (meaning we changed it previously 113 // because it's a corner case and we're being called from a different entry point). 114 if (ci.numberPresentation == Call.PRESENTATION_ALLOWED 115 || (ci.numberPresentation != presentation 116 && presentation == Call.PRESENTATION_ALLOWED)) { 117 int cnapSpecialCase = checkCnapSpecialCases(number); 118 if (cnapSpecialCase != CNAP_SPECIAL_CASE_NO) { 119 // For all special strings, change number & numberPresentation. 120 if (cnapSpecialCase == Call.PRESENTATION_RESTRICTED) { 121 number = context.getString(R.string.private_num); 122 } else if (cnapSpecialCase == Call.PRESENTATION_UNKNOWN) { 123 number = context.getString(R.string.unknown); 124 } 125 Log.d(TAG, "SpecialCnap: number=" + toLogSafePhoneNumber(number) 126 + "; presentation now=" + cnapSpecialCase); 127 ci.numberPresentation = cnapSpecialCase; 128 } 129 } 130 Log.d(TAG, "modifyForSpecialCnapCases: returning number string=" 131 + toLogSafePhoneNumber(number)); 132 return number; 133 } 134 135 /** 136 * Based on the input CNAP number string, 137 * @return _RESTRICTED or _UNKNOWN for all the special CNAP strings. 138 * Otherwise, return CNAP_SPECIAL_CASE_NO. 139 */ 140 private static int checkCnapSpecialCases(String n) { 141 if (n.equals("PRIVATE") || 142 n.equals("P") || 143 n.equals("RES")) { 144 Log.d(TAG, "checkCnapSpecialCases, PRIVATE string: " + n); 145 return Call.PRESENTATION_RESTRICTED; 146 } else if (n.equals("UNAVAILABLE") || 147 n.equals("UNKNOWN") || 148 n.equals("UNA") || 149 n.equals("U")) { 150 Log.d(TAG, "checkCnapSpecialCases, UNKNOWN string: " + n); 151 return Call.PRESENTATION_UNKNOWN; 152 } else { 153 Log.d(TAG, "checkCnapSpecialCases, normal str. number: " + n); 154 return CNAP_SPECIAL_CASE_NO; 155 } 156 } 157 158 /* package */static String toLogSafePhoneNumber(String number) { 159 // For unknown number, log empty string. 160 if (number == null) { 161 return ""; 162 } 163 164 // Todo: Figure out an equivalent for VDBG 165 if (false) { 166 // When VDBG is true we emit PII. 167 return number; 168 } 169 170 // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare 171 // sanitized phone numbers. 172 StringBuilder builder = new StringBuilder(); 173 for (int i = 0; i < number.length(); i++) { 174 char c = number.charAt(i); 175 if (c == '-' || c == '@' || c == '.' || c == '&') { 176 builder.append(c); 177 } else { 178 builder.append('x'); 179 } 180 } 181 return builder.toString(); 182 } 183 184 /** 185 * Send a notification that that we are viewing a particular contact, so that the high-res 186 * photo is downloaded by the sync adapter. 187 */ 188 public static void sendViewNotification(Context context, Uri contactUri) { 189 final Intent intent = new Intent(VIEW_NOTIFICATION_ACTION, contactUri); 190 intent.setClassName(VIEW_NOTIFICATION_PACKAGE, VIEW_NOTIFICATION_CLASS); 191 context.startService(intent); 192 } 193 } 194