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 18 package android.provider; 19 20 import android.content.ContentProvider; 21 import android.content.ContentResolver; 22 import android.content.ContentValues; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.pm.UserInfo; 26 import android.database.Cursor; 27 import android.location.Country; 28 import android.location.CountryDetector; 29 import android.net.Uri; 30 import android.os.UserHandle; 31 import android.os.UserManager; 32 import android.provider.ContactsContract.CommonDataKinds.Callable; 33 import android.provider.ContactsContract.CommonDataKinds.Phone; 34 import android.provider.ContactsContract.Data; 35 import android.provider.ContactsContract.DataUsageFeedback; 36 import android.telecom.PhoneAccountHandle; 37 import android.telephony.PhoneNumberUtils; 38 import android.text.TextUtils; 39 40 import com.android.internal.telephony.CallerInfo; 41 import com.android.internal.telephony.PhoneConstants; 42 43 import java.util.List; 44 45 /** 46 * The CallLog provider contains information about placed and received calls. 47 */ 48 public 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 = 120 "android.provider.extra.CALL_TYPE_FILTER"; 121 122 /** 123 * Content uri used to access call log entries, including voicemail records. You must have 124 * the READ_CALL_LOG and WRITE_CALL_LOG permissions to read and write to the call log, as 125 * well as READ_VOICEMAIL and WRITE_VOICEMAIL permissions to read and write voicemails. 126 */ 127 public static final Uri CONTENT_URI_WITH_VOICEMAIL = CONTENT_URI.buildUpon() 128 .appendQueryParameter(ALLOW_VOICEMAILS_PARAM_KEY, "true") 129 .build(); 130 131 /** 132 * The default sort order for this table 133 */ 134 public static final String DEFAULT_SORT_ORDER = "date DESC"; 135 136 /** 137 * The MIME type of {@link #CONTENT_URI} and {@link #CONTENT_FILTER_URI} 138 * providing a directory of calls. 139 */ 140 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/calls"; 141 142 /** 143 * The MIME type of a {@link #CONTENT_URI} sub-directory of a single 144 * call. 145 */ 146 public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/calls"; 147 148 /** 149 * The type of the call (incoming, outgoing or missed). 150 * <P>Type: INTEGER (int)</P> 151 */ 152 public static final String TYPE = "type"; 153 154 /** Call log type for incoming calls. */ 155 public static final int INCOMING_TYPE = 1; 156 /** Call log type for outgoing calls. */ 157 public static final int OUTGOING_TYPE = 2; 158 /** Call log type for missed calls. */ 159 public static final int MISSED_TYPE = 3; 160 /** Call log type for voicemails. */ 161 public static final int VOICEMAIL_TYPE = 4; 162 163 /** 164 * Bit-mask describing features of the call (e.g. video). 165 * 166 * <P>Type: INTEGER (int)</P> 167 */ 168 public static final String FEATURES = "features"; 169 170 /** Call had video. */ 171 public static final int FEATURES_VIDEO = 0x1; 172 173 /** 174 * The phone number as the user entered it. 175 * <P>Type: TEXT</P> 176 */ 177 public static final String NUMBER = "number"; 178 179 /** 180 * The number presenting rules set by the network. 181 * 182 * <p> 183 * Allowed values: 184 * <ul> 185 * <li>{@link #PRESENTATION_ALLOWED}</li> 186 * <li>{@link #PRESENTATION_RESTRICTED}</li> 187 * <li>{@link #PRESENTATION_UNKNOWN}</li> 188 * <li>{@link #PRESENTATION_PAYPHONE}</li> 189 * </ul> 190 * </p> 191 * 192 * <P>Type: INTEGER</P> 193 */ 194 public static final String NUMBER_PRESENTATION = "presentation"; 195 196 /** Number is allowed to display for caller id. */ 197 public static final int PRESENTATION_ALLOWED = 1; 198 /** Number is blocked by user. */ 199 public static final int PRESENTATION_RESTRICTED = 2; 200 /** Number is not specified or unknown by network. */ 201 public static final int PRESENTATION_UNKNOWN = 3; 202 /** Number is a pay phone. */ 203 public static final int PRESENTATION_PAYPHONE = 4; 204 205 /** 206 * The ISO 3166-1 two letters country code of the country where the 207 * user received or made the call. 208 * <P> 209 * Type: TEXT 210 * </P> 211 */ 212 public static final String COUNTRY_ISO = "countryiso"; 213 214 /** 215 * The date the call occured, in milliseconds since the epoch 216 * <P>Type: INTEGER (long)</P> 217 */ 218 public static final String DATE = "date"; 219 220 /** 221 * The duration of the call in seconds 222 * <P>Type: INTEGER (long)</P> 223 */ 224 public static final String DURATION = "duration"; 225 226 /** 227 * The data usage of the call in bytes. 228 * <P>Type: INTEGER (long)</P> 229 */ 230 public static final String DATA_USAGE = "data_usage"; 231 232 /** 233 * Whether or not the call has been acknowledged 234 * <P>Type: INTEGER (boolean)</P> 235 */ 236 public static final String NEW = "new"; 237 238 /** 239 * The cached name associated with the phone number, if it exists. 240 * This value is not guaranteed to be current, if the contact information 241 * associated with this number has changed. 242 * <P>Type: TEXT</P> 243 */ 244 public static final String CACHED_NAME = "name"; 245 246 /** 247 * The cached number type (Home, Work, etc) associated with the 248 * phone number, if it exists. 249 * This value is not guaranteed to be current, if the contact information 250 * associated with this number has changed. 251 * <P>Type: INTEGER</P> 252 */ 253 public static final String CACHED_NUMBER_TYPE = "numbertype"; 254 255 /** 256 * The cached number label, for a custom number type, associated with the 257 * phone number, if it exists. 258 * This value is not guaranteed to be current, if the contact information 259 * associated with this number has changed. 260 * <P>Type: TEXT</P> 261 */ 262 public static final String CACHED_NUMBER_LABEL = "numberlabel"; 263 264 /** 265 * URI of the voicemail entry. Populated only for {@link #VOICEMAIL_TYPE}. 266 * <P>Type: TEXT</P> 267 */ 268 public static final String VOICEMAIL_URI = "voicemail_uri"; 269 270 /** 271 * Transcription of the call or voicemail entry. This will only be populated for call log 272 * entries of type {@link #VOICEMAIL_TYPE} that have valid transcriptions. 273 */ 274 public static final String TRANSCRIPTION = "transcription"; 275 276 /** 277 * Whether this item has been read or otherwise consumed by the user. 278 * <p> 279 * Unlike the {@link #NEW} field, which requires the user to have acknowledged the 280 * existence of the entry, this implies the user has interacted with the entry. 281 * <P>Type: INTEGER (boolean)</P> 282 */ 283 public static final String IS_READ = "is_read"; 284 285 /** 286 * A geocoded location for the number associated with this call. 287 * <p> 288 * The string represents a city, state, or country associated with the number. 289 * <P>Type: TEXT</P> 290 */ 291 public static final String GEOCODED_LOCATION = "geocoded_location"; 292 293 /** 294 * The cached URI to look up the contact associated with the phone number, if it exists. 295 * This value may not be current if the contact information associated with this number 296 * has changed. 297 * <P>Type: TEXT</P> 298 */ 299 public static final String CACHED_LOOKUP_URI = "lookup_uri"; 300 301 /** 302 * The cached phone number of the contact which matches this entry, if it exists. 303 * This value may not be current if the contact information associated with this number 304 * has changed. 305 * <P>Type: TEXT</P> 306 */ 307 public static final String CACHED_MATCHED_NUMBER = "matched_number"; 308 309 /** 310 * The cached normalized(E164) version of the phone number, if it exists. 311 * This value may not be current if the contact information associated with this number 312 * has changed. 313 * <P>Type: TEXT</P> 314 */ 315 public static final String CACHED_NORMALIZED_NUMBER = "normalized_number"; 316 317 /** 318 * The cached photo id of the picture associated with the phone number, if it exists. 319 * This value may not be current if the contact information associated with this number 320 * has changed. 321 * <P>Type: INTEGER (long)</P> 322 */ 323 public static final String CACHED_PHOTO_ID = "photo_id"; 324 325 /** 326 * The cached phone number, formatted with formatting rules based on the country the 327 * user was in when the call was made or received. 328 * This value is not guaranteed to be present, and may not be current if the contact 329 * information associated with this number 330 * has changed. 331 * <P>Type: TEXT</P> 332 */ 333 public static final String CACHED_FORMATTED_NUMBER = "formatted_number"; 334 335 // Note: PHONE_ACCOUNT_* constant values are "subscription_*" due to a historic naming 336 // that was encoded into call log databases. 337 338 /** 339 * The component name of the account in string form. 340 * <P>Type: TEXT</P> 341 */ 342 public static final String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name"; 343 344 /** 345 * The identifier of a account that is unique to a specified component. 346 * <P>Type: TEXT</P> 347 */ 348 public static final String PHONE_ACCOUNT_ID = "subscription_id"; 349 350 /** 351 * The identifier of a account that is unique to a specified component. Equivalent value 352 * to {@link #PHONE_ACCOUNT_ID}. For ContactsProvider internal use only. 353 * <P>Type: INTEGER</P> 354 * 355 * @hide 356 */ 357 public static final String SUB_ID = "sub_id"; 358 359 /** 360 * If a successful call is made that is longer than this duration, update the phone number 361 * in the ContactsProvider with the normalized version of the number, based on the user's 362 * current country code. 363 */ 364 private static final int MIN_DURATION_FOR_NORMALIZED_NUMBER_UPDATE_MS = 1000 * 10; 365 366 /** 367 * Adds a call to the call log. 368 * 369 * @param ci the CallerInfo object to get the target contact from. Can be null 370 * if the contact is unknown. 371 * @param context the context used to get the ContentResolver 372 * @param number the phone number to be added to the calls db 373 * @param presentation enum value from PhoneConstants.PRESENTATION_xxx, which 374 * is set by the network and denotes the number presenting rules for 375 * "allowed", "payphone", "restricted" or "unknown" 376 * @param callType enumerated values for "incoming", "outgoing", or "missed" 377 * @param features features of the call (e.g. Video). 378 * @param accountHandle The accountHandle object identifying the provider of the call 379 * @param start time stamp for the call in milliseconds 380 * @param duration call duration in seconds 381 * @param dataUsage data usage for the call in bytes, null if data usage was not tracked for 382 * the call. 383 * @result The URI of the call log entry belonging to the user that made or received this 384 * call. 385 * {@hide} 386 */ addCall(CallerInfo ci, Context context, String number, int presentation, int callType, int features, PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage)387 public static Uri addCall(CallerInfo ci, Context context, String number, 388 int presentation, int callType, int features, PhoneAccountHandle accountHandle, 389 long start, int duration, Long dataUsage) { 390 return addCall(ci, context, number, presentation, callType, features, accountHandle, 391 start, duration, dataUsage, false); 392 } 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 */ addCall(CallerInfo ci, Context context, String number, int presentation, int callType, int features, PhoneAccountHandle accountHandle, long start, int duration, Long dataUsage, boolean addForAllUsers)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 468 if (callType == MISSED_TYPE) { 469 values.put(IS_READ, Integer.valueOf(0)); 470 } 471 if (ci != null) { 472 values.put(CACHED_NAME, ci.name); 473 values.put(CACHED_NUMBER_TYPE, ci.numberType); 474 values.put(CACHED_NUMBER_LABEL, ci.numberLabel); 475 } 476 477 if ((ci != null) && (ci.contactIdOrZero > 0)) { 478 // Update usage information for the number associated with the contact ID. 479 // We need to use both the number and the ID for obtaining a data ID since other 480 // contacts may have the same number. 481 482 final Cursor cursor; 483 484 // We should prefer normalized one (probably coming from 485 // Phone.NORMALIZED_NUMBER column) first. If it isn't available try others. 486 if (ci.normalizedNumber != null) { 487 final String normalizedPhoneNumber = ci.normalizedNumber; 488 cursor = resolver.query(Phone.CONTENT_URI, 489 new String[] { Phone._ID }, 490 Phone.CONTACT_ID + " =? AND " + Phone.NORMALIZED_NUMBER + " =?", 491 new String[] { String.valueOf(ci.contactIdOrZero), 492 normalizedPhoneNumber}, 493 null); 494 } else { 495 final String phoneNumber = ci.phoneNumber != null ? ci.phoneNumber : number; 496 cursor = resolver.query( 497 Uri.withAppendedPath(Callable.CONTENT_FILTER_URI, 498 Uri.encode(phoneNumber)), 499 new String[] { Phone._ID }, 500 Phone.CONTACT_ID + " =?", 501 new String[] { String.valueOf(ci.contactIdOrZero) }, 502 null); 503 } 504 505 if (cursor != null) { 506 try { 507 if (cursor.getCount() > 0 && cursor.moveToFirst()) { 508 final String dataId = cursor.getString(0); 509 updateDataUsageStatForData(resolver, dataId); 510 if (duration >= MIN_DURATION_FOR_NORMALIZED_NUMBER_UPDATE_MS 511 && callType == Calls.OUTGOING_TYPE 512 && TextUtils.isEmpty(ci.normalizedNumber)) { 513 updateNormalizedNumber(context, resolver, dataId, number); 514 } 515 } 516 } finally { 517 cursor.close(); 518 } 519 } 520 } 521 522 Uri result = null; 523 524 if (addForAllUsers) { 525 // Insert the entry for all currently running users, in order to trigger any 526 // ContentObservers currently set on the call log. 527 final UserManager userManager = (UserManager) context.getSystemService( 528 Context.USER_SERVICE); 529 List<UserInfo> users = userManager.getUsers(true); 530 final int currentUserId = userManager.getUserHandle(); 531 final int count = users.size(); 532 for (int i = 0; i < count; i++) { 533 final UserInfo user = users.get(i); 534 final UserHandle userHandle = user.getUserHandle(); 535 if (userManager.isUserRunning(userHandle) 536 && !userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS, 537 userHandle) 538 && !user.isManagedProfile()) { 539 Uri uri = addEntryAndRemoveExpiredEntries(context, 540 ContentProvider.maybeAddUserId(CONTENT_URI, user.id), values); 541 if (user.id == currentUserId) { 542 result = uri; 543 } 544 } 545 } 546 } else { 547 result = addEntryAndRemoveExpiredEntries(context, CONTENT_URI, values); 548 } 549 550 return result; 551 } 552 553 /** 554 * Query the call log database for the last dialed number. 555 * @param context Used to get the content resolver. 556 * @return The last phone number dialed (outgoing) or an empty 557 * string if none exist yet. 558 */ getLastOutgoingCall(Context context)559 public static String getLastOutgoingCall(Context context) { 560 final ContentResolver resolver = context.getContentResolver(); 561 Cursor c = null; 562 try { 563 c = resolver.query( 564 CONTENT_URI, 565 new String[] {NUMBER}, 566 TYPE + " = " + OUTGOING_TYPE, 567 null, 568 DEFAULT_SORT_ORDER + " LIMIT 1"); 569 if (c == null || !c.moveToFirst()) { 570 return ""; 571 } 572 return c.getString(0); 573 } finally { 574 if (c != null) c.close(); 575 } 576 } 577 addEntryAndRemoveExpiredEntries(Context context, Uri uri, ContentValues values)578 private static Uri addEntryAndRemoveExpiredEntries(Context context, Uri uri, 579 ContentValues values) { 580 final ContentResolver resolver = context.getContentResolver(); 581 Uri result = resolver.insert(uri, values); 582 resolver.delete(uri, "_id IN " + 583 "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER 584 + " LIMIT -1 OFFSET 500)", null); 585 return result; 586 } 587 updateDataUsageStatForData(ContentResolver resolver, String dataId)588 private static void updateDataUsageStatForData(ContentResolver resolver, String dataId) { 589 final Uri feedbackUri = DataUsageFeedback.FEEDBACK_URI.buildUpon() 590 .appendPath(dataId) 591 .appendQueryParameter(DataUsageFeedback.USAGE_TYPE, 592 DataUsageFeedback.USAGE_TYPE_CALL) 593 .build(); 594 resolver.update(feedbackUri, new ContentValues(), null, null); 595 } 596 597 /* 598 * Update the normalized phone number for the given dataId in the ContactsProvider, based 599 * on the user's current country. 600 */ updateNormalizedNumber(Context context, ContentResolver resolver, String dataId, String number)601 private static void updateNormalizedNumber(Context context, ContentResolver resolver, 602 String dataId, String number) { 603 if (TextUtils.isEmpty(number) || TextUtils.isEmpty(dataId)) { 604 return; 605 } 606 final String countryIso = getCurrentCountryIso(context); 607 if (TextUtils.isEmpty(countryIso)) { 608 return; 609 } 610 final String normalizedNumber = PhoneNumberUtils.formatNumberToE164(number, 611 getCurrentCountryIso(context)); 612 if (TextUtils.isEmpty(normalizedNumber)) { 613 return; 614 } 615 final ContentValues values = new ContentValues(); 616 values.put(Phone.NORMALIZED_NUMBER, normalizedNumber); 617 resolver.update(Data.CONTENT_URI, values, Data._ID + "=?", new String[] {dataId}); 618 } 619 getCurrentCountryIso(Context context)620 private static String getCurrentCountryIso(Context context) { 621 String countryIso = null; 622 final CountryDetector detector = (CountryDetector) context.getSystemService( 623 Context.COUNTRY_DETECTOR); 624 if (detector != null) { 625 final Country country = detector.detectCountry(); 626 if (country != null) { 627 countryIso = country.getCountryIso(); 628 } 629 } 630 return countryIso; 631 } 632 } 633 } 634