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 android.text.TextUtils; 26 import android.telephony.TelephonyManager; 27 import android.telephony.PhoneNumberUtils; 28 import android.util.Config; 29 import android.util.Log; 30 31 /** 32 * Looks up caller information for the given phone number. 33 * 34 * {@hide} 35 */ 36 public class CallerInfo { 37 private static final String TAG = "CallerInfo"; 38 39 public static final String UNKNOWN_NUMBER = "-1"; 40 public static final String PRIVATE_NUMBER = "-2"; 41 public static final String PAYPHONE_NUMBER = "-3"; 42 43 /** 44 * Please note that, any one of these member variables can be null, 45 * and any accesses to them should be prepared to handle such a case. 46 * 47 * Also, it is implied that phoneNumber is more often populated than 48 * name is, (think of calls being dialed/received using numbers where 49 * names are not known to the device), so phoneNumber should serve as 50 * a dependable fallback when name is unavailable. 51 * 52 * One other detail here is that this CallerInfo object reflects 53 * information found on a connection, it is an OUTPUT that serves 54 * mainly to display information to the user. In no way is this object 55 * used as input to make a connection, so we can choose to display 56 * whatever human-readable text makes sense to the user for a 57 * connection. This is especially relevant for the phone number field, 58 * since it is the one field that is most likely exposed to the user. 59 * 60 * As an example: 61 * 1. User dials "911" 62 * 2. Device recognizes that this is an emergency number 63 * 3. We use the "Emergency Number" string instead of "911" in the 64 * phoneNumber field. 65 * 66 * What we're really doing here is treating phoneNumber as an essential 67 * field here, NOT name. We're NOT always guaranteed to have a name 68 * for a connection, but the number should be displayable. 69 */ 70 public String name; 71 public String phoneNumber; 72 73 public String cnapName; 74 public int numberPresentation; 75 public int namePresentation; 76 public boolean contactExists; 77 78 public String phoneLabel; 79 /* Split up the phoneLabel into number type and label name */ 80 public int numberType; 81 public String numberLabel; 82 83 public int photoResource; 84 public long person_id; 85 public boolean needUpdate; 86 public Uri contactRefUri; 87 88 // fields to hold individual contact preference data, 89 // including the send to voicemail flag and the ringtone 90 // uri reference. 91 public Uri contactRingtoneUri; 92 public boolean shouldSendToVoicemail; 93 94 /** 95 * Drawable representing the caller image. This is essentially 96 * a cache for the image data tied into the connection / 97 * callerinfo object. The isCachedPhotoCurrent flag indicates 98 * if the image data needs to be reloaded. 99 */ 100 public Drawable cachedPhoto; 101 public boolean isCachedPhotoCurrent; 102 103 private boolean mIsEmergency; 104 private boolean mIsVoiceMail; 105 CallerInfo()106 public CallerInfo() { 107 // TODO: Move all the basic initialization here? 108 mIsEmergency = false; 109 mIsVoiceMail = false; 110 } 111 112 /** 113 * getCallerInfo given a Cursor. 114 * @param context the context used to retrieve string constants 115 * @param contactRef the URI to attach to this CallerInfo object 116 * @param cursor the first object in the cursor is used to build the CallerInfo object. 117 * @return the CallerInfo which contains the caller id for the given 118 * number. The returned CallerInfo is null if no number is supplied. 119 */ getCallerInfo(Context context, Uri contactRef, Cursor cursor)120 public static CallerInfo getCallerInfo(Context context, Uri contactRef, Cursor cursor) { 121 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 136 int columnIndex; 137 138 // Look for the name 139 columnIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME); 140 if (columnIndex != -1) { 141 info.name = cursor.getString(columnIndex); 142 } 143 144 // Look for the number 145 columnIndex = cursor.getColumnIndex(PhoneLookup.NUMBER); 146 if (columnIndex != -1) { 147 info.phoneNumber = cursor.getString(columnIndex); 148 } 149 150 // Look for the label/type combo 151 columnIndex = cursor.getColumnIndex(PhoneLookup.LABEL); 152 if (columnIndex != -1) { 153 int typeColumnIndex = cursor.getColumnIndex(PhoneLookup.TYPE); 154 if (typeColumnIndex != -1) { 155 info.numberType = cursor.getInt(typeColumnIndex); 156 info.numberLabel = cursor.getString(columnIndex); 157 info.phoneLabel = Phone.getDisplayLabel(context, 158 info.numberType, info.numberLabel) 159 .toString(); 160 } 161 } 162 163 // Look for the person ID 164 columnIndex = cursor.getColumnIndex(PhoneLookup._ID); 165 if (columnIndex != -1) { 166 info.person_id = cursor.getLong(columnIndex); 167 } 168 169 // look for the custom ringtone, create from the string stored 170 // in the database. 171 columnIndex = cursor.getColumnIndex(PhoneLookup.CUSTOM_RINGTONE); 172 if ((columnIndex != -1) && (cursor.getString(columnIndex) != null)) { 173 info.contactRingtoneUri = Uri.parse(cursor.getString(columnIndex)); 174 } else { 175 info.contactRingtoneUri = null; 176 } 177 178 // look for the send to voicemail flag, set it to true only 179 // under certain circumstances. 180 columnIndex = cursor.getColumnIndex(PhoneLookup.SEND_TO_VOICEMAIL); 181 info.shouldSendToVoicemail = (columnIndex != -1) && 182 ((cursor.getInt(columnIndex)) == 1); 183 info.contactExists = true; 184 } 185 cursor.close(); 186 } 187 188 info.needUpdate = false; 189 info.name = normalize(info.name); 190 info.contactRefUri = contactRef; 191 192 return info; 193 } 194 195 /** 196 * getCallerInfo given a URI, look up in the call-log database 197 * for the uri unique key. 198 * @param context the context used to get the ContentResolver 199 * @param contactRef the URI used to lookup caller id 200 * @return the CallerInfo which contains the caller id for the given 201 * number. The returned CallerInfo is null if no number is supplied. 202 */ getCallerInfo(Context context, Uri contactRef)203 public static CallerInfo getCallerInfo(Context context, Uri contactRef) { 204 205 return getCallerInfo(context, contactRef, 206 context.getContentResolver().query(contactRef, null, null, null, null)); 207 } 208 209 /** 210 * getCallerInfo given a phone number, look up in the call-log database 211 * for the matching caller id info. 212 * @param context the context used to get the ContentResolver 213 * @param number the phone number used to lookup caller id 214 * @return the CallerInfo which contains the caller id for the given 215 * number. The returned CallerInfo is null if no number is supplied. If 216 * a matching number is not found, then a generic caller info is returned, 217 * with all relevant fields empty or null. 218 */ getCallerInfo(Context context, String number)219 public static CallerInfo getCallerInfo(Context context, String number) { 220 if (TextUtils.isEmpty(number)) { 221 return null; 222 } 223 224 // Change the callerInfo number ONLY if it is an emergency number 225 // or if it is the voicemail number. If it is either, take a 226 // shortcut and skip the query. 227 if (PhoneNumberUtils.isEmergencyNumber(number)) { 228 return new CallerInfo().markAsEmergency(context); 229 } else if (PhoneNumberUtils.isVoiceMailNumber(number)) { 230 return new CallerInfo().markAsVoiceMail(); 231 } 232 233 Uri contactUri = Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, Uri.encode(number)); 234 235 CallerInfo info = getCallerInfo(context, contactUri); 236 237 // if no query results were returned with a viable number, 238 // fill in the original number value we used to query with. 239 if (TextUtils.isEmpty(info.phoneNumber)) { 240 info.phoneNumber = number; 241 } 242 243 return info; 244 } 245 246 /** 247 * getCallerId: a convenience method to get the caller id for a given 248 * number. 249 * 250 * @param context the context used to get the ContentResolver. 251 * @param number a phone number. 252 * @return if the number belongs to a contact, the contact's name is 253 * returned; otherwise, the number itself is returned. 254 * 255 * TODO NOTE: This MAY need to refer to the Asynchronous Query API 256 * [startQuery()], instead of getCallerInfo, but since it looks like 257 * it is only being used by the provider calls in the messaging app: 258 * 1. android.provider.Telephony.Mms.getDisplayAddress() 259 * 2. android.provider.Telephony.Sms.getDisplayAddress() 260 * We may not need to make the change. 261 */ getCallerId(Context context, String number)262 public static String getCallerId(Context context, String number) { 263 CallerInfo info = getCallerInfo(context, number); 264 String callerID = null; 265 266 if (info != null) { 267 String name = info.name; 268 269 if (!TextUtils.isEmpty(name)) { 270 callerID = name; 271 } else { 272 callerID = number; 273 } 274 } 275 276 return callerID; 277 } 278 279 // Accessors 280 281 /** 282 * @return true if the caller info is an emergency number. 283 */ isEmergencyNumber()284 public boolean isEmergencyNumber() { 285 return mIsEmergency; 286 } 287 288 /** 289 * @return true if the caller info is a voicemail number. 290 */ isVoiceMailNumber()291 public boolean isVoiceMailNumber() { 292 return mIsVoiceMail; 293 } 294 295 /** 296 * Mark this CallerInfo as an emergency call. 297 * @param context To lookup the localized 'Emergency Number' string. 298 * @return this instance. 299 */ 300 // TODO: Note we're setting the phone number here (refer to 301 // javadoc comments at the top of CallerInfo class) to a localized 302 // string 'Emergency Number'. This is pretty bad because we are 303 // making UI work here instead of just packaging the data. We 304 // should set the phone number to the dialed number and name to 305 // 'Emergency Number' and let the UI make the decision about what 306 // should be displayed. markAsEmergency(Context context)307 /* package */ CallerInfo markAsEmergency(Context context) { 308 phoneNumber = context.getString( 309 com.android.internal.R.string.emergency_call_dialog_number_for_display); 310 photoResource = com.android.internal.R.drawable.picture_emergency; 311 mIsEmergency = true; 312 return this; 313 } 314 315 316 /** 317 * Mark this CallerInfo as a voicemail call. The voicemail label 318 * is obtained from the telephony manager. Caller must hold the 319 * READ_PHONE_STATE permission otherwise the phoneNumber will be 320 * set to null. 321 * @return this instance. 322 */ 323 // TODO: As in the emergency number handling, we end up writing a 324 // string in the phone number field. markAsVoiceMail()325 /* package */ CallerInfo markAsVoiceMail() { 326 mIsVoiceMail = true; 327 328 try { 329 String voiceMailLabel = TelephonyManager.getDefault().getVoiceMailAlphaTag(); 330 331 phoneNumber = voiceMailLabel; 332 } catch (SecurityException se) { 333 // Should never happen: if this process does not have 334 // permission to retrieve VM tag, it should not have 335 // permission to retrieve VM number and would not call 336 // this method. 337 // Leave phoneNumber untouched. 338 Log.e(TAG, "Cannot access VoiceMail.", se); 339 } 340 // TODO: There is no voicemail picture? 341 // FIXME: FIND ANOTHER ICON 342 // photoResource = android.R.drawable.badge_voicemail; 343 return this; 344 } 345 normalize(String s)346 private static String normalize(String s) { 347 if (s == null || s.length() > 0) { 348 return s; 349 } else { 350 return null; 351 } 352 } 353 354 /** 355 * @return a string debug representation of this instance. 356 */ toString()357 public String toString() { 358 return new StringBuilder(384) 359 .append("\nname: " + name) 360 .append("\nphoneNumber: " + phoneNumber) 361 .append("\ncnapName: " + cnapName) 362 .append("\nnumberPresentation: " + numberPresentation) 363 .append("\nnamePresentation: " + namePresentation) 364 .append("\ncontactExits: " + contactExists) 365 .append("\nphoneLabel: " + phoneLabel) 366 .append("\nnumberType: " + numberType) 367 .append("\nnumberLabel: " + numberLabel) 368 .append("\nphotoResource: " + photoResource) 369 .append("\nperson_id: " + person_id) 370 .append("\nneedUpdate: " + needUpdate) 371 .append("\ncontactRefUri: " + contactRefUri) 372 .append("\ncontactRingtoneUri: " + contactRefUri) 373 .append("\nshouldSendToVoicemail: " + shouldSendToVoicemail) 374 .append("\ncachedPhoto: " + cachedPhoto) 375 .append("\nisCachedPhotoCurrent: " + isCachedPhotoCurrent) 376 .append("\nemergency: " + mIsEmergency) 377 .append("\nvoicemail " + mIsVoiceMail) 378 .toString(); 379 } 380 } 381