Home | History | Annotate | Download | only in telephony
      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 
     17 package com.android.internal.telephony;
     18 
     19 import android.content.Context;
     20 import android.database.Cursor;
     21 import android.graphics.drawable.Drawable;
     22 import android.net.Uri;
     23 import android.provider.ContactsContract.PhoneLookup;
     24 import android.provider.ContactsContract.CommonDataKinds.Phone;
     25 import static android.provider.ContactsContract.RawContacts;
     26 import android.text.TextUtils;
     27 import android.telephony.TelephonyManager;
     28 import android.telephony.PhoneNumberUtils;
     29 import android.util.Config;
     30 import android.util.Log;
     31 
     32 /**
     33  * Looks up caller information for the given phone number.
     34  *
     35  * {@hide}
     36  */
     37 public 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