CallerInfo.java revision c72509b4d815e23bfa563cfe96e04f54f2a221fe
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.Config;
30import android.util.Log;
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
40    public static final String UNKNOWN_NUMBER = "-1";
41    public static final String PRIVATE_NUMBER = "-2";
42    public static final String PAYPHONE_NUMBER = "-3";
43
44    /**
45     * Please note that, any one of these member variables can be null,
46     * and any accesses to them should be prepared to handle such a case.
47     *
48     * Also, it is implied that phoneNumber is more often populated than
49     * name is, (think of calls being dialed/received using numbers where
50     * names are not known to the device), so phoneNumber should serve as
51     * a dependable fallback when name is unavailable.
52     *
53     * One other detail here is that this CallerInfo object reflects
54     * information found on a connection, it is an OUTPUT that serves
55     * mainly to display information to the user.  In no way is this object
56     * used as input to make a connection, so we can choose to display
57     * whatever human-readable text makes sense to the user for a
58     * connection.  This is especially relevant for the phone number field,
59     * since it is the one field that is most likely exposed to the user.
60     *
61     * As an example:
62     *   1. User dials "911"
63     *   2. Device recognizes that this is an emergency number
64     *   3. We use the "Emergency Number" string instead of "911" in the
65     *     phoneNumber field.
66     *
67     * What we're really doing here is treating phoneNumber as an essential
68     * field here, NOT name.  We're NOT always guaranteed to have a name
69     * for a connection, but the number should be displayable.
70     */
71    public String name;
72    public String phoneNumber;
73
74    public String cnapName;
75    public int numberPresentation;
76    public int namePresentation;
77    public boolean contactExists;
78
79    public String phoneLabel;
80    /* Split up the phoneLabel into number type and label name */
81    public int    numberType;
82    public String numberLabel;
83
84    public int photoResource;
85    public long person_id;
86    public boolean needUpdate;
87    public Uri contactRefUri;
88
89    // fields to hold individual contact preference data,
90    // including the send to voicemail flag and the ringtone
91    // uri reference.
92    public Uri contactRingtoneUri;
93    public boolean shouldSendToVoicemail;
94
95    /**
96     * Drawable representing the caller image.  This is essentially
97     * a cache for the image data tied into the connection /
98     * callerinfo object.  The isCachedPhotoCurrent flag indicates
99     * if the image data needs to be reloaded.
100     */
101    public Drawable cachedPhoto;
102    public boolean isCachedPhotoCurrent;
103
104    private boolean mIsEmergency;
105    private boolean mIsVoiceMail;
106
107    public CallerInfo() {
108        // TODO: Move all the basic initialization here?
109        mIsEmergency = false;
110        mIsVoiceMail = false;
111    }
112
113    /**
114     * getCallerInfo given a Cursor.
115     * @param context the context used to retrieve string constants
116     * @param contactRef the URI to attach to this CallerInfo object
117     * @param cursor the first object in the cursor is used to build the CallerInfo object.
118     * @return the CallerInfo which contains the caller id for the given
119     * number. The returned CallerInfo is null if no number is supplied.
120     */
121    public static CallerInfo getCallerInfo(Context context, Uri contactRef, Cursor cursor) {
122        CallerInfo info = new CallerInfo();
123        info.photoResource = 0;
124        info.phoneLabel = null;
125        info.numberType = 0;
126        info.numberLabel = null;
127        info.cachedPhoto = null;
128        info.isCachedPhotoCurrent = false;
129        info.contactExists = false;
130
131        if (Config.LOGV) Log.v(TAG, "construct callerInfo from cursor");
132
133        if (cursor != null) {
134            if (cursor.moveToFirst()) {
135                // TODO: photo_id is always available but not taken
136                // care of here. Maybe we should store it in the
137                // CallerInfo object as well.
138
139                int columnIndex;
140
141                // Look for the name
142                columnIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
143                if (columnIndex != -1) {
144                    info.name = cursor.getString(columnIndex);
145                }
146
147                // Look for the number
148                columnIndex = cursor.getColumnIndex(PhoneLookup.NUMBER);
149                if (columnIndex != -1) {
150                    info.phoneNumber = cursor.getString(columnIndex);
151                }
152
153                // Look for the label/type combo
154                columnIndex = cursor.getColumnIndex(PhoneLookup.LABEL);
155                if (columnIndex != -1) {
156                    int typeColumnIndex = cursor.getColumnIndex(PhoneLookup.TYPE);
157                    if (typeColumnIndex != -1) {
158                        info.numberType = cursor.getInt(typeColumnIndex);
159                        info.numberLabel = cursor.getString(columnIndex);
160                        info.phoneLabel = Phone.getDisplayLabel(context,
161                                info.numberType, info.numberLabel)
162                                .toString();
163                    }
164                }
165
166                // Look for the person ID.
167
168                // TODO: This is pretty ugly now, see bug 2269240 for
169                // more details. With tel: URI the contact id is in
170                // col "_id" while when we use a
171                // content://contacts/data/phones URI, the contact id
172                // is col "contact_id". As a work around we use the
173                // type of the contact url to figure out which column
174                // we should look at to get the contact_id.
175
176                final String mimeType = context.getContentResolver().getType(contactRef);
177
178                columnIndex = -1;
179                if (Phone.CONTENT_ITEM_TYPE.equals(mimeType)) {
180                    // content://com.android.contacts/data/phones URL
181                    columnIndex = cursor.getColumnIndex(RawContacts.CONTACT_ID);
182                } else {
183                    // content://com.android.contacts/phone_lookup URL
184                    // TODO: mime type is null here so we cannot test
185                    // if we have the right url type. phone_lookup URL
186                    // should resolve to a mime type.
187                    columnIndex = cursor.getColumnIndex(PhoneLookup._ID);
188                }
189
190                if (columnIndex != -1) {
191                    info.person_id = cursor.getLong(columnIndex);
192                } else {
193                    Log.e(TAG, "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
264        // if no query results were returned with a viable number,
265        // fill in the original number value we used to query with.
266        if (TextUtils.isEmpty(info.phoneNumber)) {
267            info.phoneNumber = number;
268        }
269
270        return info;
271    }
272
273    /**
274     * getCallerId: a convenience method to get the caller id for a given
275     * number.
276     *
277     * @param context the context used to get the ContentResolver.
278     * @param number a phone number.
279     * @return if the number belongs to a contact, the contact's name is
280     * returned; otherwise, the number itself is returned.
281     *
282     * TODO NOTE: This MAY need to refer to the Asynchronous Query API
283     * [startQuery()], instead of getCallerInfo, but since it looks like
284     * it is only being used by the provider calls in the messaging app:
285     *   1. android.provider.Telephony.Mms.getDisplayAddress()
286     *   2. android.provider.Telephony.Sms.getDisplayAddress()
287     * We may not need to make the change.
288     */
289    public static String getCallerId(Context context, String number) {
290        CallerInfo info = getCallerInfo(context, number);
291        String callerID = null;
292
293        if (info != null) {
294            String name = info.name;
295
296            if (!TextUtils.isEmpty(name)) {
297                callerID = name;
298            } else {
299                callerID = number;
300            }
301        }
302
303        return callerID;
304    }
305
306    // Accessors
307
308    /**
309     * @return true if the caller info is an emergency number.
310     */
311    public boolean isEmergencyNumber() {
312        return mIsEmergency;
313    }
314
315    /**
316     * @return true if the caller info is a voicemail number.
317     */
318    public boolean isVoiceMailNumber() {
319        return mIsVoiceMail;
320    }
321
322    /**
323     * Mark this CallerInfo as an emergency call.
324     * @param context To lookup the localized 'Emergency Number' string.
325     * @return this instance.
326     */
327    // TODO: Note we're setting the phone number here (refer to
328    // javadoc comments at the top of CallerInfo class) to a localized
329    // string 'Emergency Number'. This is pretty bad because we are
330    // making UI work here instead of just packaging the data. We
331    // should set the phone number to the dialed number and name to
332    // 'Emergency Number' and let the UI make the decision about what
333    // should be displayed.
334    /* package */ CallerInfo markAsEmergency(Context context) {
335        phoneNumber = context.getString(
336            com.android.internal.R.string.emergency_call_dialog_number_for_display);
337        photoResource = com.android.internal.R.drawable.picture_emergency;
338        mIsEmergency = true;
339        return this;
340    }
341
342
343    /**
344     * Mark this CallerInfo as a voicemail call. The voicemail label
345     * is obtained from the telephony manager. Caller must hold the
346     * READ_PHONE_STATE permission otherwise the phoneNumber will be
347     * set to null.
348     * @return this instance.
349     */
350    // TODO: As in the emergency number handling, we end up writing a
351    // string in the phone number field.
352    /* package */ CallerInfo markAsVoiceMail() {
353        mIsVoiceMail = true;
354
355        try {
356            String voiceMailLabel = TelephonyManager.getDefault().getVoiceMailAlphaTag();
357
358            phoneNumber = voiceMailLabel;
359        } catch (SecurityException se) {
360            // Should never happen: if this process does not have
361            // permission to retrieve VM tag, it should not have
362            // permission to retrieve VM number and would not call
363            // this method.
364            // Leave phoneNumber untouched.
365            Log.e(TAG, "Cannot access VoiceMail.", se);
366        }
367        // TODO: There is no voicemail picture?
368        // FIXME: FIND ANOTHER ICON
369        // photoResource = android.R.drawable.badge_voicemail;
370        return this;
371    }
372
373    private static String normalize(String s) {
374        if (s == null || s.length() > 0) {
375            return s;
376        } else {
377            return null;
378        }
379    }
380
381    /**
382     * @return a string debug representation of this instance.
383     */
384    public String toString() {
385        return new StringBuilder(384)
386                .append("\nname: " + name)
387                .append("\nphoneNumber: " + phoneNumber)
388                .append("\ncnapName: " + cnapName)
389                .append("\nnumberPresentation: " + numberPresentation)
390                .append("\nnamePresentation: " + namePresentation)
391                .append("\ncontactExits: " + contactExists)
392                .append("\nphoneLabel: " + phoneLabel)
393                .append("\nnumberType: " + numberType)
394                .append("\nnumberLabel: " + numberLabel)
395                .append("\nphotoResource: " + photoResource)
396                .append("\nperson_id: " + person_id)
397                .append("\nneedUpdate: " + needUpdate)
398                .append("\ncontactRefUri: " + contactRefUri)
399                .append("\ncontactRingtoneUri: " + contactRefUri)
400                .append("\nshouldSendToVoicemail: " + shouldSendToVoicemail)
401                .append("\ncachedPhoto: " + cachedPhoto)
402                .append("\nisCachedPhotoCurrent: " + isCachedPhotoCurrent)
403                .append("\nemergency: " + mIsEmergency)
404                .append("\nvoicemail " + mIsVoiceMail)
405                .toString();
406    }
407}
408