summaryrefslogtreecommitdiff
path: root/InCallUI/src/com/android/incallui/CallerInfoUtils.java
blob: 690b9b7b7d8859c82a1fcd4f60de66ed1080162e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
package com.android.incallui;

import android.content.Context;
import android.text.TextUtils;

import com.android.services.telephony.common.Call;

import java.util.Arrays;

/**
 * TODO: Insert description here. (generated by yorkelee)
 */
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;

    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, Call call,
            CallerInfoAsyncQuery.OnQueryCompleteListener listener) {
        CallerInfo info = new CallerInfo();
        String number = call.getNumber();

        // Store CNAP information retrieved from the Connection (we want to do this
        // here regardless of whether the number is empty or not).
        info.cnapName = call.getCnapName();
        info.name = info.cnapName;
        info.numberPresentation = call.getNumberPresentation();
        info.namePresentation = call.getCnapNamePresentation();

        // TODO: Have phoneapp send a Uri when it knows the contact that triggered this call.

        if (!TextUtils.isEmpty(number)) {
            number = modifyForSpecialCnapCases(context, info, number, info.numberPresentation);
            info.phoneNumber = number;

            // For scenarios where we may receive a valid number from the network but a
            // restricted/unavailable presentation, we do not want to perform a contact query,
            // so just return the existing caller info.
            if (info.numberPresentation != Call.PRESENTATION_ALLOWED) {
                return info;
            } else {
                // 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);
            }
        } else {
            // The number is null or empty (Blocked caller id or empty). Just return the
            // caller info object as is, without starting a query.
            return info;
        }

        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 (klp): 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 == '.') {
                builder.append(c);
            } else {
                builder.append('x');
            }
        }
        return builder.toString();
    }
}