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