1package com.android.incallui;
2
3import android.content.Context;
4import android.content.Intent;
5import android.net.Uri;
6import android.text.TextUtils;
7
8import com.android.services.telephony.common.Call;
9import com.android.services.telephony.common.CallIdentification;
10
11import java.util.Arrays;
12
13/**
14 * Utility methods for contact and caller info related functionality
15 */
16public 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