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