CallerInfoUtils.java revision 5ea0dcd39ff1f29d09d7bb146d7eb20f24b32201
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 (CallUtil.SCHEME_VOICEMAIL.equals(call.getHandle().getScheme())) { 78 info.markAsVoiceMail(context); 79 } 80 81 return info; 82 } 83 84 /** 85 * Handles certain "corner cases" for CNAP. When we receive weird phone numbers 86 * from the network to indicate different number presentations, convert them to 87 * expected number and presentation values within the CallerInfo object. 88 * @param number number we use to verify if we are in a corner case 89 * @param presentation presentation value used to verify if we are in a corner case 90 * @return the new String that should be used for the phone number 91 */ 92 /* package */static String modifyForSpecialCnapCases(Context context, CallerInfo ci, 93 String number, int presentation) { 94 // Obviously we return number if ci == null, but still return number if 95 // number == null, because in these cases the correct string will still be 96 // displayed/logged after this function returns based on the presentation value. 97 if (ci == null || number == null) return number; 98 99 Log.d(TAG, "modifyForSpecialCnapCases: initially, number=" 100 + toLogSafePhoneNumber(number) 101 + ", presentation=" + presentation + " ci " + ci); 102 103 // "ABSENT NUMBER" is a possible value we could get from the network as the 104 // phone number, so if this happens, change it to "Unknown" in the CallerInfo 105 // and fix the presentation to be the same. 106 final String[] absentNumberValues = 107 context.getResources().getStringArray(R.array.absent_num); 108 if (Arrays.asList(absentNumberValues).contains(number) 109 && presentation == PropertyPresentation.ALLOWED) { 110 number = context.getString(R.string.unknown); 111 ci.numberPresentation = PropertyPresentation.UNKNOWN; 112 } 113 114 // Check for other special "corner cases" for CNAP and fix them similarly. Corner 115 // cases only apply if we received an allowed presentation from the network, so check 116 // if we think we have an allowed presentation, or if the CallerInfo presentation doesn't 117 // match the presentation passed in for verification (meaning we changed it previously 118 // because it's a corner case and we're being called from a different entry point). 119 if (ci.numberPresentation == PropertyPresentation.ALLOWED 120 || (ci.numberPresentation != presentation 121 && presentation == PropertyPresentation.ALLOWED)) { 122 // For all special strings, change number & numberPrentation. 123 if (isCnapSpecialCaseRestricted(number)) { 124 number = context.getString(R.string.private_num); 125 ci.numberPresentation = PropertyPresentation.RESTRICTED; 126 } else if (isCnapSpecialCaseUnknown(number)) { 127 number = context.getString(R.string.unknown); 128 ci.numberPresentation = PropertyPresentation.UNKNOWN; 129 } 130 Log.d(TAG, "SpecialCnap: number=" + toLogSafePhoneNumber(number) 131 + "; presentation now=" + ci.numberPresentation); 132 } 133 Log.d(TAG, "modifyForSpecialCnapCases: returning number string=" 134 + toLogSafePhoneNumber(number)); 135 return number; 136 } 137 138 private static boolean isCnapSpecialCaseRestricted(String n) { 139 return n.equals("PRIVATE") || n.equals("P") || n.equals("RES"); 140 } 141 142 private static boolean isCnapSpecialCaseUnknown(String n) { 143 return n.equals("UNAVAILABLE") || n.equals("UNKNOWN") || n.equals("UNA") || n.equals("U"); 144 } 145 146 /* package */static String toLogSafePhoneNumber(String number) { 147 // For unknown number, log empty string. 148 if (number == null) { 149 return ""; 150 } 151 152 // Todo: Figure out an equivalent for VDBG 153 if (false) { 154 // When VDBG is true we emit PII. 155 return number; 156 } 157 158 // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare 159 // sanitized phone numbers. 160 StringBuilder builder = new StringBuilder(); 161 for (int i = 0; i < number.length(); i++) { 162 char c = number.charAt(i); 163 if (c == '-' || c == '@' || c == '.' || c == '&') { 164 builder.append(c); 165 } else { 166 builder.append('x'); 167 } 168 } 169 return builder.toString(); 170 } 171 172 /** 173 * Send a notification using a {@link ContactLoader} to inform the sync adapter that we are 174 * viewing a particular contact, so that it can download the high-res photo. 175 */ 176 public static void sendViewNotification(Context context, Uri contactUri) { 177 final ContactLoader loader = new ContactLoader(context, contactUri, 178 true /* postViewNotification */); 179 loader.registerListener(0, new OnLoadCompleteListener<Contact>() { 180 @Override 181 public void onLoadComplete( 182 Loader<Contact> loader, Contact contact) { 183 try { 184 loader.reset(); 185 } catch (RuntimeException e) { 186 Log.e(TAG, "Error resetting loader", e); 187 } 188 } 189 }); 190 loader.startLoading(); 191 } 192} 193