CallerInfo.java revision 6fe795ecd35c4d49822d349424fc71b660577dfc
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.internal.telephony;
18
19import android.content.Context;
20import android.database.Cursor;
21import android.graphics.drawable.Drawable;
22import android.net.Uri;
23import android.provider.ContactsContract.PhoneLookup;
24import android.provider.ContactsContract.CommonDataKinds.Phone;
25import static android.provider.ContactsContract.RawContacts;
26import android.text.TextUtils;
27import android.telephony.TelephonyManager;
28import android.telephony.PhoneNumberUtils;
29import android.util.Log;
30
31
32/**
33 * Looks up caller information for the given phone number.
34 *
35 * {@hide}
36 */
37public class CallerInfo {
38    private static final String TAG = "CallerInfo";
39    private static final boolean VDBG = Log.isLoggable(TAG, Log.VERBOSE);
40
41    public static final String UNKNOWN_NUMBER = "-1";
42    public static final String PRIVATE_NUMBER = "-2";
43    public static final String PAYPHONE_NUMBER = "-3";
44
45    /**
46     * Please note that, any one of these member variables can be null,
47     * and any accesses to them should be prepared to handle such a case.
48     *
49     * Also, it is implied that phoneNumber is more often populated than
50     * name is, (think of calls being dialed/received using numbers where
51     * names are not known to the device), so phoneNumber should serve as
52     * a dependable fallback when name is unavailable.
53     *
54     * One other detail here is that this CallerInfo object reflects
55     * information found on a connection, it is an OUTPUT that serves
56     * mainly to display information to the user.  In no way is this object
57     * used as input to make a connection, so we can choose to display
58     * whatever human-readable text makes sense to the user for a
59     * connection.  This is especially relevant for the phone number field,
60     * since it is the one field that is most likely exposed to the user.
61     *
62     * As an example:
63     *   1. User dials "911"
64     *   2. Device recognizes that this is an emergency number
65     *   3. We use the "Emergency Number" string instead of "911" in the
66     *     phoneNumber field.
67     *
68     * What we're really doing here is treating phoneNumber as an essential
69     * field here, NOT name.  We're NOT always guaranteed to have a name
70     * for a connection, but the number should be displayable.
71     */
72    public String name;
73    public String phoneNumber;
74
75    public String cnapName;
76    public int numberPresentation;
77    public int namePresentation;
78    public boolean contactExists;
79
80    public String phoneLabel;
81    /* Split up the phoneLabel into number type and label name */
82    public int    numberType;
83    public String numberLabel;
84
85    public int photoResource;
86    public long person_id;
87    public boolean needUpdate;
88    public Uri contactRefUri;
89
90    // fields to hold individual contact preference data,
91    // including the send to voicemail flag and the ringtone
92    // uri reference.
93    public Uri contactRingtoneUri;
94    public boolean shouldSendToVoicemail;
95
96    /**
97     * Drawable representing the caller image.  This is essentially
98     * a cache for the image data tied into the connection /
99     * callerinfo object.  The isCachedPhotoCurrent flag indicates
100     * if the image data needs to be reloaded.
101     */
102    public Drawable cachedPhoto;
103    public boolean isCachedPhotoCurrent;
104
105    private boolean mIsEmergency;
106    private boolean mIsVoiceMail;
107
108    public CallerInfo() {
109        // TODO: Move all the basic initialization here?
110        mIsEmergency = false;
111        mIsVoiceMail = false;
112    }
113
114    /**
115     * getCallerInfo given a Cursor.
116     * @param context the context used to retrieve string constants
117     * @param contactRef the URI to attach to this CallerInfo object
118     * @param cursor the first object in the cursor is used to build the CallerInfo object.
119     * @return the CallerInfo which contains the caller id for the given
120     * number. The returned CallerInfo is null if no number is supplied.
121     */
122    public static CallerInfo getCallerInfo(Context context, Uri contactRef, Cursor cursor) {
123        CallerInfo info = new CallerInfo();
124        info.photoResource = 0;
125        info.phoneLabel = null;
126        info.numberType = 0;
127        info.numberLabel = null;
128        info.cachedPhoto = null;
129        info.isCachedPhotoCurrent = false;
130        info.contactExists = false;
131
132        if (VDBG) Log.v(TAG, "construct callerInfo from cursor");
133
134        if (cursor != null) {
135            if (cursor.moveToFirst()) {
136                // TODO: photo_id is always available but not taken
137                // care of here. Maybe we should store it in the
138                // CallerInfo object as well.
139
140                int columnIndex;
141
142                // Look for the name
143                columnIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
144                if (columnIndex != -1) {
145                    info.name = cursor.getString(columnIndex);
146                }
147
148                // Look for the number
149                columnIndex = cursor.getColumnIndex(PhoneLookup.NUMBER);
150                if (columnIndex != -1) {
151                    info.phoneNumber = cursor.getString(columnIndex);
152                }
153
154                // Look for the label/type combo
155                columnIndex = cursor.getColumnIndex(PhoneLookup.LABEL);
156                if (columnIndex != -1) {
157                    int typeColumnIndex = cursor.getColumnIndex(PhoneLookup.TYPE);
158                    if (typeColumnIndex != -1) {
159                        info.numberType = cursor.getInt(typeColumnIndex);
160                        info.numberLabel = cursor.getString(columnIndex);
161                        info.phoneLabel = Phone.getDisplayLabel(context,
162                                info.numberType, info.numberLabel)
163                                .toString();
164                    }
165                }
166
167                // Look for the person ID.
168
169                // TODO: This is pretty ugly now, see bug 2269240 for
170                // more details. The column to use depends upon the type of URL,
171                // for content://com.android.contacts/data/phones the "contact_id"
172                // column is used. For content/com.andriod.contacts/phone_lookup"
173                // the "_ID" column is used. If it is neither we leave columnIndex
174                // at -1 and no person ID will be available.
175
176                columnIndex = -1;
177                String url = contactRef.toString();
178                if (url.startsWith("content://com.android.contacts/data/phones")) {
179                    if (VDBG) Log.v(TAG,
180                        "URL path starts with 'data/phones' using RawContacts.CONTACT_ID");
181                    columnIndex = cursor.getColumnIndex(RawContacts.CONTACT_ID);
182                } else if (url.startsWith("content://com.android.contacts/phone_lookup")) {
183                    if (VDBG) Log.v(TAG,
184                        "URL path starts with 'phone_lookup' using PhoneLookup._ID");
185                    columnIndex = cursor.getColumnIndex(PhoneLookup._ID);
186                } else {
187                    Log.e(TAG, "Bad contact URL '" + url + "'");
188                }
189
190                if (columnIndex != -1) {
191                    info.person_id = cursor.getLong(columnIndex);
192                } else {
193                    Log.e(TAG, "person_id column missing for " + contactRef);
194                }
195
196                // look for the custom ringtone, create from the string stored
197                // in the database.
198                columnIndex = cursor.getColumnIndex(PhoneLookup.CUSTOM_RINGTONE);
199                if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) {
200                    info.contactRingtoneUri = Uri.parse(cursor.getString(columnIndex));
201                } else {
202                    info.contactRingtoneUri = null;
203                }
204
205                // look for the send to voicemail flag, set it to true only
206                // under certain circumstances.
207                columnIndex = cursor.getColumnIndex(PhoneLookup.SEND_TO_VOICEMAIL);
208                info.shouldSendToVoicemail = (columnIndex != -1) &&
209                        ((cursor.getInt(columnIndex)) == 1);
210                info.contactExists = true;
211            }
212            cursor.close();
213        }
214
215        info.needUpdate = false;
216        info.name = normalize(info.name);
217        info.contactRefUri = contactRef;
218
219        return info;
220    }
221
222    /**
223     * getCallerInfo given a URI, look up in the call-log database
224     * for the uri unique key.
225     * @param context the context used to get the ContentResolver
226     * @param contactRef the URI used to lookup caller id
227     * @return the CallerInfo which contains the caller id for the given
228     * number. The returned CallerInfo is null if no number is supplied.
229     */
230    public static CallerInfo getCallerInfo(Context context, Uri contactRef) {
231
232        return getCallerInfo(context, contactRef,
233                context.getContentResolver().query(contactRef, null, null, null, null));
234    }
235
236    /**
237     * getCallerInfo given a phone number, look up in the call-log database
238     * for the matching caller id info.
239     * @param context the context used to get the ContentResolver
240     * @param number the phone number used to lookup caller id
241     * @return the CallerInfo which contains the caller id for the given
242     * number. The returned CallerInfo is null if no number is supplied. If
243     * a matching number is not found, then a generic caller info is returned,
244     * with all relevant fields empty or null.
245     */
246    public static CallerInfo getCallerInfo(Context context, String number) {
247        if (TextUtils.isEmpty(number)) {
248            return null;
249        }
250
251        // Change the callerInfo number ONLY if it is an emergency number
252        // or if it is the voicemail number.  If it is either, take a
253        // shortcut and skip the query.
254        if (PhoneNumberUtils.isEmergencyNumber(number)) {
255            return new CallerInfo().markAsEmergency(context);
256        } else if (PhoneNumberUtils.isVoiceMailNumber(number)) {
257            return new CallerInfo().markAsVoiceMail();
258        }
259
260        Uri contactUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number));
261
262        CallerInfo info = getCallerInfo(context, contactUri);
263        info = doSecondaryLookupIfNecessary(context, number, info);
264
265        // if no query results were returned with a viable number,
266        // fill in the original number value we used to query with.
267        if (TextUtils.isEmpty(info.phoneNumber)) {
268            info.phoneNumber = number;
269        }
270
271        return info;
272    }
273
274    /**
275     * Performs another lookup if previous lookup fails and it's a SIP call
276     * and the peer's username is all numeric. Look up the username as it
277     * could be a PSTN number in the contact database.
278     *
279     * @param context the query context
280     * @param number the original phone number, could be a SIP URI
281     * @param previousResult the result of previous lookup
282     * @return previousResult if it's not the case
283     */
284    static CallerInfo doSecondaryLookupIfNecessary(Context context,
285            String number, CallerInfo previousResult) {
286        if (!previousResult.contactExists
287                && PhoneNumberUtils.isUriNumber(number)) {
288            String username = number.substring(0, number.indexOf('@'));
289            if (PhoneNumberUtils.isGlobalPhoneNumber(username)) {
290                previousResult = getCallerInfo(context,
291                        Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI,
292                                Uri.encode(username)));
293            }
294        }
295        return previousResult;
296    }
297
298    /**
299     * getCallerId: a convenience method to get the caller id for a given
300     * number.
301     *
302     * @param context the context used to get the ContentResolver.
303     * @param number a phone number.
304     * @return if the number belongs to a contact, the contact's name is
305     * returned; otherwise, the number itself is returned.
306     *
307     * TODO NOTE: This MAY need to refer to the Asynchronous Query API
308     * [startQuery()], instead of getCallerInfo, but since it looks like
309     * it is only being used by the provider calls in the messaging app:
310     *   1. android.provider.Telephony.Mms.getDisplayAddress()
311     *   2. android.provider.Telephony.Sms.getDisplayAddress()
312     * We may not need to make the change.
313     */
314    public static String getCallerId(Context context, String number) {
315        CallerInfo info = getCallerInfo(context, number);
316        String callerID = null;
317
318        if (info != null) {
319            String name = info.name;
320
321            if (!TextUtils.isEmpty(name)) {
322                callerID = name;
323            } else {
324                callerID = number;
325            }
326        }
327
328        return callerID;
329    }
330
331    // Accessors
332
333    /**
334     * @return true if the caller info is an emergency number.
335     */
336    public boolean isEmergencyNumber() {
337        return mIsEmergency;
338    }
339
340    /**
341     * @return true if the caller info is a voicemail number.
342     */
343    public boolean isVoiceMailNumber() {
344        return mIsVoiceMail;
345    }
346
347    /**
348     * Mark this CallerInfo as an emergency call.
349     * @param context To lookup the localized 'Emergency Number' string.
350     * @return this instance.
351     */
352    // TODO: Note we're setting the phone number here (refer to
353    // javadoc comments at the top of CallerInfo class) to a localized
354    // string 'Emergency Number'. This is pretty bad because we are
355    // making UI work here instead of just packaging the data. We
356    // should set the phone number to the dialed number and name to
357    // 'Emergency Number' and let the UI make the decision about what
358    // should be displayed.
359    /* package */ CallerInfo markAsEmergency(Context context) {
360        phoneNumber = context.getString(
361            com.android.internal.R.string.emergency_call_dialog_number_for_display);
362        photoResource = com.android.internal.R.drawable.picture_emergency;
363        mIsEmergency = true;
364        return this;
365    }
366
367
368    /**
369     * Mark this CallerInfo as a voicemail call. The voicemail label
370     * is obtained from the telephony manager. Caller must hold the
371     * READ_PHONE_STATE permission otherwise the phoneNumber will be
372     * set to null.
373     * @return this instance.
374     */
375    // TODO: As in the emergency number handling, we end up writing a
376    // string in the phone number field.
377    /* package */ CallerInfo markAsVoiceMail() {
378        mIsVoiceMail = true;
379
380        try {
381            String voiceMailLabel = TelephonyManager.getDefault().getVoiceMailAlphaTag();
382
383            phoneNumber = voiceMailLabel;
384        } catch (SecurityException se) {
385            // Should never happen: if this process does not have
386            // permission to retrieve VM tag, it should not have
387            // permission to retrieve VM number and would not call
388            // this method.
389            // Leave phoneNumber untouched.
390            Log.e(TAG, "Cannot access VoiceMail.", se);
391        }
392        // TODO: There is no voicemail picture?
393        // FIXME: FIND ANOTHER ICON
394        // photoResource = android.R.drawable.badge_voicemail;
395        return this;
396    }
397
398    private static String normalize(String s) {
399        if (s == null || s.length() > 0) {
400            return s;
401        } else {
402            return null;
403        }
404    }
405
406    /**
407     * @return a string debug representation of this instance.
408     */
409    public String toString() {
410        return new StringBuilder(384)
411                .append("\nname: " + /*name*/ "nnnnnn")
412                .append("\nphoneNumber: " + /*phoneNumber*/ "xxxxxxx")
413                .append("\ncnapName: " + cnapName)
414                .append("\nnumberPresentation: " + numberPresentation)
415                .append("\nnamePresentation: " + namePresentation)
416                .append("\ncontactExits: " + contactExists)
417                .append("\nphoneLabel: " + phoneLabel)
418                .append("\nnumberType: " + numberType)
419                .append("\nnumberLabel: " + numberLabel)
420                .append("\nphotoResource: " + photoResource)
421                .append("\nperson_id: " + person_id)
422                .append("\nneedUpdate: " + needUpdate)
423                .append("\ncontactRefUri: " + /*contactRefUri*/ "xxxxxxx")
424                .append("\ncontactRingtoneUri: " + /*contactRefUri*/ "xxxxxxx")
425                .append("\nshouldSendToVoicemail: " + shouldSendToVoicemail)
426                .append("\ncachedPhoto: " + cachedPhoto)
427                .append("\nisCachedPhotoCurrent: " + isCachedPhotoCurrent)
428                .append("\nemergency: " + mIsEmergency)
429                .append("\nvoicemail " + mIsVoiceMail)
430                .append("\ncontactExists " + contactExists)
431                .toString();
432    }
433}
434