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