CallLog.java revision d5005842348efd0f7e86b67692cf0638a10a7fed
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 no associated features (e.g. voice-only). */ 167 public static final int FEATURES_NONE = 0x0; 168 /** Call had video. */ 169 public static final int FEATURES_VIDEO = 0x1; 170 171 /** 172 * The phone number as the user entered it. 173 * <P>Type: TEXT</P> 174 */ 175 public static final String NUMBER = "number"; 176 177 /** 178 * The number presenting rules set by the network. 179 * 180 * <p> 181 * Allowed values: 182 * <ul> 183 * <li>{@link #PRESENTATION_ALLOWED}</li> 184 * <li>{@link #PRESENTATION_RESTRICTED}</li> 185 * <li>{@link #PRESENTATION_UNKNOWN}</li> 186 * <li>{@link #PRESENTATION_PAYPHONE}</li> 187 * </ul> 188 * </p> 189 * 190 * <P>Type: INTEGER</P> 191 */ 192 public static final String NUMBER_PRESENTATION = "presentation"; 193 194 /** Number is allowed to display for caller id. */ 195 public static final int PRESENTATION_ALLOWED = 1; 196 /** Number is blocked by user. */ 197 public static final int PRESENTATION_RESTRICTED = 2; 198 /** Number is not specified or unknown by network. */ 199 public static final int PRESENTATION_UNKNOWN = 3; 200 /** Number is a pay phone. */ 201 public static final int PRESENTATION_PAYPHONE = 4; 202 203 /** 204 * The ISO 3166-1 two letters country code of the country where the 205 * user received or made the call. 206 * <P> 207 * Type: TEXT 208 * </P> 209 */ 210 public static final String COUNTRY_ISO = "countryiso"; 211 212 /** 213 * The date the call occured, in milliseconds since the epoch 214 * <P>Type: INTEGER (long)</P> 215 */ 216 public static final String DATE = "date"; 217 218 /** 219 * The duration of the call in seconds 220 * <P>Type: INTEGER (long)</P> 221 */ 222 public static final String DURATION = "duration"; 223 224 /** 225 * The data usage of the call in bytes. 226 * <P>Type: INTEGER (long)</P> 227 */ 228 public static final String DATA_USAGE = "data_usage"; 229 230 /** 231 * Whether or not the call has been acknowledged 232 * <P>Type: INTEGER (boolean)</P> 233 */ 234 public static final String NEW = "new"; 235 236 /** 237 * The cached name associated with the phone number, if it exists. 238 * This value is not guaranteed to be current, if the contact information 239 * associated with this number has changed. 240 * <P>Type: TEXT</P> 241 */ 242 public static final String CACHED_NAME = "name"; 243 244 /** 245 * The cached number type (Home, Work, etc) associated with the 246 * phone number, if it exists. 247 * This value is not guaranteed to be current, if the contact information 248 * associated with this number has changed. 249 * <P>Type: INTEGER</P> 250 */ 251 public static final String CACHED_NUMBER_TYPE = "numbertype"; 252 253 /** 254 * The cached number label, for a custom number type, associated with the 255 * phone number, if it exists. 256 * This value is not guaranteed to be current, if the contact information 257 * associated with this number has changed. 258 * <P>Type: TEXT</P> 259 */ 260 public static final String CACHED_NUMBER_LABEL = "numberlabel"; 261 262 /** 263 * URI of the voicemail entry. Populated only for {@link #VOICEMAIL_TYPE}. 264 * <P>Type: TEXT</P> 265 */ 266 public static final String VOICEMAIL_URI = "voicemail_uri"; 267 268 /** 269 * Transcription of the call or voicemail entry. This will only be populated for call log 270 * entries of type {@link #VOICEMAIL_TYPE} that have valid transcriptions. 271 */ 272 public static final String TRANSCRIPTION = "transcription"; 273 274 /** 275 * Whether this item has been read or otherwise consumed by the user. 276 * <p> 277 * Unlike the {@link #NEW} field, which requires the user to have acknowledged the 278 * existence of the entry, this implies the user has interacted with the entry. 279 * <P>Type: INTEGER (boolean)</P> 280 */ 281 public static final String IS_READ = "is_read"; 282 283 /** 284 * A geocoded location for the number associated with this call. 285 * <p> 286 * The string represents a city, state, or country associated with the number. 287 * <P>Type: TEXT</P> 288 */ 289 public static final String GEOCODED_LOCATION = "geocoded_location"; 290 291 /** 292 * The cached URI to look up the contact associated with the phone number, if it exists. 293 * This value may not be current if the contact information associated with this number 294 * has changed. 295 * <P>Type: TEXT</P> 296 */ 297 public static final String CACHED_LOOKUP_URI = "lookup_uri"; 298 299 /** 300 * The cached phone number of the contact which matches this entry, if it exists. 301 * This value may not be current if the contact information associated with this number 302 * has changed. 303 * <P>Type: TEXT</P> 304 */ 305 public static final String CACHED_MATCHED_NUMBER = "matched_number"; 306 307 /** 308 * The cached normalized(E164) version of the phone number, if it exists. 309 * This value may not be current if the contact information associated with this number 310 * has changed. 311 * <P>Type: TEXT</P> 312 */ 313 public static final String CACHED_NORMALIZED_NUMBER = "normalized_number"; 314 315 /** 316 * The cached photo id of the picture associated with the phone number, if it exists. 317 * This value may not be current if the contact information associated with this number 318 * has changed. 319 * <P>Type: INTEGER (long)</P> 320 */ 321 public static final String CACHED_PHOTO_ID = "photo_id"; 322 323 /** 324 * The cached phone number, formatted with formatting rules based on the country the 325 * user was in when the call was made or received. 326 * This value is not guaranteed to be present, and may not be current if the contact 327 * information associated with this number 328 * has changed. 329 * <P>Type: TEXT</P> 330 */ 331 public static final String CACHED_FORMATTED_NUMBER = "formatted_number"; 332 333 // Note: PHONE_ACCOUNT_* constant values are "subscription_*" due to a historic naming 334 // that was encoded into call log databases. 335 336 /** 337 * The component name of the account in string form. 338 * <P>Type: TEXT</P> 339 */ 340 public static final String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name"; 341 342 /** 343 * The identifier of a account that is unique to a specified component. 344 * <P>Type: TEXT</P> 345 */ 346 public static final String PHONE_ACCOUNT_ID = "subscription_id"; 347 348 /** 349 * The identifier of a account that is unique to a specified component. Equivalent value 350 * to {@link #PHONE_ACCOUNT_ID}. For ContactsProvider internal use only. 351 * <P>Type: INTEGER</P> 352 * 353 * @hide 354 */ 355 public static final String SUB_ID = "sub_id"; 356 357 /** 358 * If a successful call is made that is longer than this duration, update the phone number 359 * in the ContactsProvider with the normalized version of the number, based on the user's 360 * current country code. 361 */ 362 private static final int MIN_DURATION_FOR_NORMALIZED_NUMBER_UPDATE_MS = 1000 * 10; 363 364 /** 365 * Adds a call to the call log. 366 * 367 * @param ci the CallerInfo object to get the target contact from. Can be null 368 * if the contact is unknown. 369 * @param context the context used to get the ContentResolver 370 * @param number the phone number to be added to the calls db 371 * @param presentation enum value from PhoneConstants.PRESENTATION_xxx, which 372 * is set by the network and denotes the number presenting rules for 373 * "allowed", "payphone", "restricted" or "unknown" 374 * @param callType enumerated values for "incoming", "outgoing", or "missed" 375 * @param features features of the call (e.g. Video). 376 * @param accountHandle The accountHandle object identifying the provider of the call 377 * @param start time stamp for the call in milliseconds 378 * @param duration call duration in seconds 379 * @param dataUsage data usage for the call in bytes, null if data usage was not tracked for 380 * the call. 381 * @result The URI of the call log entry belonging to the user that made or received this 382 * call. 383 * {@hide} 384 */ 385 public static Uri addCall(CallerInfo ci, Context context, String number, 386 int presentation, int callType, int features, PhoneAccountHandle accountHandle, 387 long start, int duration, Long dataUsage) { 388 // FIXME using -1 as subId instead of SubscriptionManager.INVALID_SUB_ID 389 return addCall(ci, context, number, presentation, callType, features, accountHandle, 390 start, duration, dataUsage, false); 391 } 392 393 394 /** 395 * Adds a call to the call log. 396 * 397 * @param ci the CallerInfo object to get the target contact from. Can be null 398 * if the contact is unknown. 399 * @param context the context used to get the ContentResolver 400 * @param number the phone number to be added to the calls db 401 * @param presentation enum value from PhoneConstants.PRESENTATION_xxx, which 402 * is set by the network and denotes the number presenting rules for 403 * "allowed", "payphone", "restricted" or "unknown" 404 * @param callType enumerated values for "incoming", "outgoing", or "missed" 405 * @param features features of the call (e.g. Video). 406 * @param accountHandle The accountHandle object identifying the provider of the call 407 * @param start time stamp for the call in milliseconds 408 * @param duration call duration in seconds 409 * @param subId the subscription id. 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 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 Uri feedbackUri = DataUsageFeedback.FEEDBACK_URI.buildUpon() 509 .appendPath(cursor.getString(0)) 510 .appendQueryParameter(DataUsageFeedback.USAGE_TYPE, 511 DataUsageFeedback.USAGE_TYPE_CALL) 512 .build(); 513 resolver.update(feedbackUri, new ContentValues(), null, null); 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} 588