blob: 3d2c5c4a755ce773c27b92322ea3e4a3eaff7df1 [file] [log] [blame]
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);
}
}