CallLog.java revision 8dc2c62821c4e3fbec5d1528874bd244651a632e
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 18package android.provider; 19 20import android.content.ContentProvider; 21import android.content.ContentResolver; 22import android.content.ContentValues; 23import android.content.Context; 24import android.content.Intent; 25import android.content.pm.UserInfo; 26import android.database.Cursor; 27import android.net.Uri; 28import android.os.UserHandle; 29import android.os.UserManager; 30import android.provider.ContactsContract.CommonDataKinds.Callable; 31import android.provider.ContactsContract.CommonDataKinds.Phone; 32import android.provider.ContactsContract.DataUsageFeedback; 33import android.telecomm.PhoneAccountHandle; 34import android.text.TextUtils; 35 36import com.android.internal.telephony.CallerInfo; 37import com.android.internal.telephony.PhoneConstants; 38 39import java.util.List; 40 41/** 42 * The CallLog provider contains information about placed and received calls. 43 */ 44public class CallLog { 45 public static final String AUTHORITY = "call_log"; 46 47 /** 48 * The content:// style URL for this provider 49 */ 50 public static final Uri CONTENT_URI = 51 Uri.parse("content://" + AUTHORITY); 52 53 /** 54 * Contains the recent calls. 55 */ 56 public static class Calls implements BaseColumns { 57 /** 58 * The content:// style URL for this table 59 */ 60 public static final Uri CONTENT_URI = 61 Uri.parse("content://call_log/calls"); 62 63 /** 64 * The content:// style URL for filtering this table on phone numbers 65 */ 66 public static final Uri CONTENT_FILTER_URI = 67 Uri.parse("content://call_log/calls/filter"); 68 69 /** 70 * Query parameter used to limit the number of call logs returned. 71 * <p> 72 * TYPE: integer 73 */ 74 public static final String LIMIT_PARAM_KEY = "limit"; 75 76 /** 77 * Query parameter used to specify the starting record to return. 78 * <p> 79 * TYPE: integer 80 */ 81 public static final String OFFSET_PARAM_KEY = "offset"; 82 83 /** 84 * An optional URI parameter which instructs the provider to allow the operation to be 85 * applied to voicemail records as well. 86 * <p> 87 * TYPE: Boolean 88 * <p> 89 * Using this parameter with a value of {@code true} will result in a security error if the 90 * calling package does not have appropriate permissions to access voicemails. 91 * 92 * @hide 93 */ 94 public static final String ALLOW_VOICEMAILS_PARAM_KEY = "allow_voicemails"; 95 96 /** 97 * An optional extra used with {@link #CONTENT_TYPE Calls.CONTENT_TYPE} and 98 * {@link Intent#ACTION_VIEW} to specify that the presented list of calls should be 99 * filtered for a particular call type. 100 * 101 * Applications implementing a call log UI should check for this extra, and display a 102 * filtered list of calls based on the specified call type. If not applicable within the 103 * application's UI, it should be silently ignored. 104 * 105 * <p> 106 * The following example brings up the call log, showing only missed calls. 107 * <pre> 108 * Intent intent = new Intent(Intent.ACTION_VIEW); 109 * intent.setType(CallLog.Calls.CONTENT_TYPE); 110 * intent.putExtra(CallLog.Calls.EXTRA_CALL_TYPE_FILTER, CallLog.Calls.MISSED_TYPE); 111 * startActivity(intent); 112 * </pre> 113 * </p> 114 */ 115 public static final String EXTRA_CALL_TYPE_FILTER = 116 "android.provider.extra.CALL_TYPE_FILTER"; 117 118 /** 119 * Content uri used to access call log entries, including voicemail records. You must have 120 * the READ_CALL_LOG and WRITE_CALL_LOG permissions to read and write to the call log, as 121 * well as READ_VOICEMAIL and WRITE_VOICEMAIL permissions to read and write voicemails. 122 */ 123 public static final Uri CONTENT_URI_WITH_VOICEMAIL = CONTENT_URI.buildUpon() 124 .appendQueryParameter(ALLOW_VOICEMAILS_PARAM_KEY, "true") 125 .build(); 126 127 /** 128 * The default sort order for this table 129 */ 130 public static final String DEFAULT_SORT_ORDER = "date DESC"; 131 132 /** 133 * The MIME type of {@link #CONTENT_URI} and {@link #CONTENT_FILTER_URI} 134 * providing a directory of calls. 135 */ 136 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/calls"; 137 138 /** 139 * The MIME type of a {@link #CONTENT_URI} sub-directory of a single 140 * call. 141 */ 142 public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/calls"; 143 144 /** 145 * The type of the call (incoming, outgoing or missed). 146 * <P>Type: INTEGER (int)</P> 147 */ 148 public static final String TYPE = "type"; 149 150 /** Call log type for incoming calls. */ 151 public static final int INCOMING_TYPE = 1; 152 /** Call log type for outgoing calls. */ 153 public static final int OUTGOING_TYPE = 2; 154 /** Call log type for missed calls. */ 155 public static final int MISSED_TYPE = 3; 156 /** Call log type for voicemails. */ 157 public static final int VOICEMAIL_TYPE = 4; 158 159 /** 160 * Bit-mask describing features of the call (e.g. video). 161 * 162 * <P>Type: INTEGER (int)</P> 163 */ 164 public static final String FEATURES = "features"; 165 166 /** Call had video. */ 167 public static final int FEATURES_VIDEO = 0x1; 168 169 /** 170 * The phone number as the user entered it. 171 * <P>Type: TEXT</P> 172 */ 173 public static final String NUMBER = "number"; 174 175 /** 176 * The number presenting rules set by the network. 177 * 178 * <p> 179 * Allowed values: 180 * <ul> 181 * <li>{@link #PRESENTATION_ALLOWED}</li> 182 * <li>{@link #PRESENTATION_RESTRICTED}</li> 183 * <li>{@link #PRESENTATION_UNKNOWN}</li> 184 * <li>{@link #PRESENTATION_PAYPHONE}</li> 185 * </ul> 186 * </p> 187 * 188 * <P>Type: INTEGER</P> 189 */ 190 public static final String NUMBER_PRESENTATION = "presentation"; 191 192 /** Number is allowed to display for caller id. */ 193 public static final int PRESENTATION_ALLOWED = 1; 194 /** Number is blocked by user. */ 195 public static final int PRESENTATION_RESTRICTED = 2; 196 /** Number is not specified or unknown by network. */ 197 public static final int PRESENTATION_UNKNOWN = 3; 198 /** Number is a pay phone. */ 199 public static final int PRESENTATION_PAYPHONE = 4; 200 201 /** 202 * The ISO 3166-1 two letters country code of the country where the 203 * user received or made the call. 204 * <P> 205 * Type: TEXT 206 * </P> 207 */ 208 public static final String COUNTRY_ISO = "countryiso"; 209 210 /** 211 * The date the call occured, in milliseconds since the epoch 212 * <P>Type: INTEGER (long)</P> 213 */ 214 public static final String DATE = "date"; 215 216 /** 217 * The duration of the call in seconds 218 * <P>Type: INTEGER (long)</P> 219 */ 220 public static final String DURATION = "duration"; 221 222 /** 223 * The data usage of the call in bytes. 224 * <P>Type: INTEGER (long)</P> 225 */ 226 public static final String DATA_USAGE = "data_usage"; 227 228 /** 229 * Whether or not the call has been acknowledged 230 * <P>Type: INTEGER (boolean)</P> 231 */ 232 public static final String NEW = "new"; 233 234 /** 235 * The cached name associated with the phone number, if it exists. 236 * This value is not guaranteed to be current, if the contact information 237 * associated with this number has changed. 238 * <P>Type: TEXT</P> 239 */ 240 public static final String CACHED_NAME = "name"; 241 242 /** 243 * The cached number type (Home, Work, etc) associated with the 244 * phone number, if it exists. 245 * This value is not guaranteed to be current, if the contact information 246 * associated with this number has changed. 247 * <P>Type: INTEGER</P> 248 */ 249 public static final String CACHED_NUMBER_TYPE = "numbertype"; 250 251 /** 252 * The cached number label, for a custom number type, associated with the 253 * phone number, if it exists. 254 * This value is not guaranteed to be current, if the contact information 255 * associated with this number has changed. 256 * <P>Type: TEXT</P> 257 */ 258 public static final String CACHED_NUMBER_LABEL = "numberlabel"; 259 260 /** 261 * URI of the voicemail entry. Populated only for {@link #VOICEMAIL_TYPE}. 262 * <P>Type: TEXT</P> 263 */ 264 public static final String VOICEMAIL_URI = "voicemail_uri"; 265 266 /** 267 * Transcription of the call or voicemail entry. This will only be populated for call log 268 * entries of type {@link #VOICEMAIL_TYPE} that have valid transcriptions. 269 */ 270 public static final String TRANSCRIPTION = "transcription"; 271 272 /** 273 * Whether this item has been read or otherwise consumed by the user. 274 * <p> 275 * Unlike the {@link #NEW} field, which requires the user to have acknowledged the 276 * existence of the entry, this implies the user has interacted with the entry. 277 * <P>Type: INTEGER (boolean)</P> 278 */ 279 public static final String IS_READ = "is_read"; 280 281 /** 282 * A geocoded location for the number associated with this call. 283 * <p> 284 * The string represents a city, state, or country associated with the number. 285 * <P>Type: TEXT</P> 286 */ 287 public static final String GEOCODED_LOCATION = "geocoded_location"; 288 289 /** 290 * The cached URI to look up the contact associated with the phone number, if it exists. 291 * This value may not be current if the contact information associated with this number 292 * has changed. 293 * <P>Type: TEXT</P> 294 */ 295 public static final String CACHED_LOOKUP_URI = "lookup_uri"; 296 297 /** 298 * The cached phone number of the contact which matches this entry, if it exists. 299 * This value may not be current if the contact information associated with this number 300 * has changed. 301 * <P>Type: TEXT</P> 302 */ 303 public static final String CACHED_MATCHED_NUMBER = "matched_number"; 304 305 /** 306 * The cached normalized(E164) version of the phone number, if it exists. 307 * This value may not be current if the contact information associated with this number 308 * has changed. 309 * <P>Type: TEXT</P> 310 */ 311 public static final String CACHED_NORMALIZED_NUMBER = "normalized_number"; 312 313 /** 314 * The cached photo id of the picture associated with the phone number, if it exists. 315 * This value may not be current if the contact information associated with this number 316 * has changed. 317 * <P>Type: INTEGER (long)</P> 318 */ 319 public static final String CACHED_PHOTO_ID = "photo_id"; 320 321 /** 322 * The cached phone number, formatted with formatting rules based on the country the 323 * user was in when the call was made or received. 324 * This value is not guaranteed to be present, and may not be current if the contact 325 * information associated with this number 326 * has changed. 327 * <P>Type: TEXT</P> 328 */ 329 public static final String CACHED_FORMATTED_NUMBER = "formatted_number"; 330 331 // Note: PHONE_ACCOUNT_* constant values are "subscription_*" due to a historic naming 332 // that was encoded into call log databases. 333 334 /** 335 * The component name of the account in string form. 336 * <P>Type: TEXT</P> 337 */ 338 public static final String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name"; 339 340 /** 341 * The identifier of a account that is unique to a specified component. 342 * <P>Type: TEXT</P> 343 */ 344 public static final String PHONE_ACCOUNT_ID = "subscription_id"; 345 346 /** 347 * The identifier of a account that is unique to a specified component. Equivalent value 348 * to {@link #PHONE_ACCOUNT_ID}. For ContactsProvider internal use only. 349 * <P>Type: INTEGER</P> 350 * 351 * @hide 352 */ 353 public static final String SUB_ID = "sub_id"; 354 355 /** 356 * If a successful call is made that is longer than this duration, update the phone number 357 * in the ContactsProvider with the normalized version of the number, based on the user's 358 * current country code. 359 */ 360 private static final int MIN_DURATION_FOR_NORMALIZED_NUMBER_UPDATE_MS = 1000 * 10; 361 362 /** 363 * Adds a call to the call log. 364 * 365 * @param ci the CallerInfo object to get the target contact from. Can be null 366 * if the contact is unknown. 367 * @param context the context used to get the ContentResolver 368 * @param number the phone number to be added to the calls db 369 * @param presentation enum value from PhoneConstants.PRESENTATION_xxx, which 370 * is set by the network and denotes the number presenting rules for 371 * "allowed", "payphone", "restricted" or "unknown" 372 * @param callType enumerated values for "incoming", "outgoing", or "missed" 373 * @param features features of the call (e.g. Video). 374 * @param accountHandle The accountHandle object identifying the provider of the call 375 * @param start time stamp for the call in milliseconds 376 * @param duration call duration in seconds 377 * @param dataUsage data usage for the call in bytes, null if data usage was not tracked for 378 * the call. 379 * @result The URI of the call log entry belonging to the user that made or received this 380 * call. 381 * {@hide} 382 */ 383 public static Uri addCall(CallerInfo ci, Context context, String number, 384 int presentation, int callType, int features, PhoneAccountHandle accountHandle, 385 long start, int duration, Long dataUsage) { 386 // FIXME using -1 as subId instead of SubscriptionManager.INVALID_SUB_ID 387 return addCall(ci, context, number, presentation, callType, features, accountHandle, 388 start, duration, dataUsage, false); 389 } 390 391 392 /** 393 * Adds a call to the call log. 394 * 395 * @param ci the CallerInfo object to get the target contact from. Can be null 396 * if the contact is unknown. 397 * @param context the context used to get the ContentResolver 398 * @param number the phone number to be added to the calls db 399 * @param presentation enum value from PhoneConstants.PRESENTATION_xxx, which 400 * is set by the network and denotes the number presenting rules for 401 * "allowed", "payphone", "restricted" or "unknown" 402 * @param callType enumerated values for "incoming", "outgoing", or "missed" 403 * @param features features of the call (e.g. Video). 404 * @param accountHandle The accountHandle object identifying the provider of the call 405 * @param start time stamp for the call in milliseconds 406 * @param duration call duration in seconds 407 * @param subId the subscription id. 408 * @param dataUsage data usage for the call in bytes, null if data usage was not tracked for 409 * the call. 410 * @param addForAllUsers If true, the call is added to the call log of all currently 411 * running users. The caller must have the MANAGE_USERS permission if this is true. 412 * 413 * @result The URI of the call log entry belonging to the user that made or received this 414 * call. 415 * {@hide} 416 */ 417 public static Uri addCall(CallerInfo ci, Context context, String number, 418 int presentation, int callType, int features, PhoneAccountHandle accountHandle, 419 long start, int duration, Long dataUsage, boolean addForAllUsers) { 420 final ContentResolver resolver = context.getContentResolver(); 421 int numberPresentation = PRESENTATION_ALLOWED; 422 423 // Remap network specified number presentation types 424 // PhoneConstants.PRESENTATION_xxx to calllog number presentation types 425 // Calls.PRESENTATION_xxx, in order to insulate the persistent calllog 426 // from any future radio changes. 427 // If the number field is empty set the presentation type to Unknown. 428 if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) { 429 numberPresentation = PRESENTATION_RESTRICTED; 430 } else if (presentation == PhoneConstants.PRESENTATION_PAYPHONE) { 431 numberPresentation = PRESENTATION_PAYPHONE; 432 } else if (TextUtils.isEmpty(number) 433 || presentation == PhoneConstants.PRESENTATION_UNKNOWN) { 434 numberPresentation = PRESENTATION_UNKNOWN; 435 } 436 if (numberPresentation != PRESENTATION_ALLOWED) { 437 number = ""; 438 if (ci != null) { 439 ci.name = ""; 440 } 441 } 442 443 // accountHandle information 444 String accountComponentString = null; 445 String accountId = null; 446 if (accountHandle != null) { 447 accountComponentString = accountHandle.getComponentName().flattenToString(); 448 accountId = accountHandle.getId(); 449 } 450 451 ContentValues values = new ContentValues(6); 452 453 values.put(NUMBER, number); 454 values.put(NUMBER_PRESENTATION, Integer.valueOf(numberPresentation)); 455 values.put(TYPE, Integer.valueOf(callType)); 456 values.put(FEATURES, features); 457 values.put(DATE, Long.valueOf(start)); 458 values.put(DURATION, Long.valueOf(duration)); 459 if (dataUsage != null) { 460 values.put(DATA_USAGE, dataUsage); 461 } 462 values.put(PHONE_ACCOUNT_COMPONENT_NAME, accountComponentString); 463 values.put(PHONE_ACCOUNT_ID, accountId); 464 values.put(NEW, Integer.valueOf(1)); 465 466 if (callType == MISSED_TYPE) { 467 values.put(IS_READ, Integer.valueOf(0)); 468 } 469 if (ci != null) { 470 values.put(CACHED_NAME, ci.name); 471 values.put(CACHED_NUMBER_TYPE, ci.numberType); 472 values.put(CACHED_NUMBER_LABEL, ci.numberLabel); 473 } 474 475 if ((ci != null) && (ci.contactIdOrZero > 0)) { 476 // Update usage information for the number associated with the contact ID. 477 // We need to use both the number and the ID for obtaining a data ID since other 478 // contacts may have the same number. 479 480 final Cursor cursor; 481 482 // We should prefer normalized one (probably coming from 483 // Phone.NORMALIZED_NUMBER column) first. If it isn't available try others. 484 if (ci.normalizedNumber != null) { 485 final String normalizedPhoneNumber = ci.normalizedNumber; 486 cursor = resolver.query(Phone.CONTENT_URI, 487 new String[] { Phone._ID }, 488 Phone.CONTACT_ID + " =? AND " + Phone.NORMALIZED_NUMBER + " =?", 489 new String[] { String.valueOf(ci.contactIdOrZero), 490 normalizedPhoneNumber}, 491 null); 492 } else { 493 final String phoneNumber = ci.phoneNumber != null ? ci.phoneNumber : number; 494 cursor = resolver.query( 495 Uri.withAppendedPath(Callable.CONTENT_FILTER_URI, 496 Uri.encode(phoneNumber)), 497 new String[] { Phone._ID }, 498 Phone.CONTACT_ID + " =?", 499 new String[] { String.valueOf(ci.contactIdOrZero) }, 500 null); 501 } 502 503 if (cursor != null) { 504 try { 505 if (cursor.getCount() > 0 && cursor.moveToFirst()) { 506 final Uri feedbackUri = DataUsageFeedback.FEEDBACK_URI.buildUpon() 507 .appendPath(cursor.getString(0)) 508 .appendQueryParameter(DataUsageFeedback.USAGE_TYPE, 509 DataUsageFeedback.USAGE_TYPE_CALL) 510 .build(); 511 resolver.update(feedbackUri, new ContentValues(), null, null); 512 } 513 } finally { 514 cursor.close(); 515 } 516 } 517 } 518 519 Uri result = null; 520 521 if (addForAllUsers) { 522 // Insert the entry for all currently running users, in order to trigger any 523 // ContentObservers currently set on the call log. 524 final UserManager userManager = (UserManager) context.getSystemService( 525 Context.USER_SERVICE); 526 List<UserInfo> users = userManager.getUsers(true); 527 final int currentUserId = userManager.getUserHandle(); 528 final int count = users.size(); 529 for (int i = 0; i < count; i++) { 530 final UserInfo user = users.get(i); 531 final UserHandle userHandle = user.getUserHandle(); 532 if (userManager.isUserRunning(userHandle) 533 && !userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, 534 userHandle) 535 && !user.isManagedProfile()) { 536 Uri uri = addEntryAndRemoveExpiredEntries(context, 537 ContentProvider.maybeAddUserId(CONTENT_URI, user.id), values); 538 if (user.id == currentUserId) { 539 result = uri; 540 } 541 } 542 } 543 } else { 544 result = addEntryAndRemoveExpiredEntries(context, CONTENT_URI, values); 545 } 546 547 return result; 548 } 549 550 /** 551 * Query the call log database for the last dialed number. 552 * @param context Used to get the content resolver. 553 * @return The last phone number dialed (outgoing) or an empty 554 * string if none exist yet. 555 */ 556 public static String getLastOutgoingCall(Context context) { 557 final ContentResolver resolver = context.getContentResolver(); 558 Cursor c = null; 559 try { 560 c = resolver.query( 561 CONTENT_URI, 562 new String[] {NUMBER}, 563 TYPE + " = " + OUTGOING_TYPE, 564 null, 565 DEFAULT_SORT_ORDER + " LIMIT 1"); 566 if (c == null || !c.moveToFirst()) { 567 return ""; 568 } 569 return c.getString(0); 570 } finally { 571 if (c != null) c.close(); 572 } 573 } 574 575 private static Uri addEntryAndRemoveExpiredEntries(Context context, Uri uri, 576 ContentValues values) { 577 final ContentResolver resolver = context.getContentResolver(); 578 Uri result = resolver.insert(uri, values); 579 resolver.delete(uri, "_id IN " + 580 "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER 581 + " LIMIT -1 OFFSET 500)", null); 582 return result; 583 } 584 } 585} 586