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.ContentResolver; 21 import android.content.ContentValues; 22 import android.content.Context; 23 import android.database.Cursor; 24 import android.net.Uri; 25 import android.provider.ContactsContract.CommonDataKinds.Callable; 26 import android.provider.ContactsContract.CommonDataKinds.Phone; 27 import android.provider.ContactsContract.DataUsageFeedback; 28 import android.text.TextUtils; 29 30 import com.android.internal.telephony.CallerInfo; 31 import com.android.internal.telephony.PhoneConstants; 32 33 /** 34 * The CallLog provider contains information about placed and received calls. 35 */ 36 public class CallLog { 37 public static final String AUTHORITY = "call_log"; 38 39 /** 40 * The content:// style URL for this provider 41 */ 42 public static final Uri CONTENT_URI = 43 Uri.parse("content://" + AUTHORITY); 44 45 /** 46 * Contains the recent calls. 47 */ 48 public static class Calls implements BaseColumns { 49 /** 50 * The content:// style URL for this table 51 */ 52 public static final Uri CONTENT_URI = 53 Uri.parse("content://call_log/calls"); 54 55 /** 56 * The content:// style URL for filtering this table on phone numbers 57 */ 58 public static final Uri CONTENT_FILTER_URI = 59 Uri.parse("content://call_log/calls/filter"); 60 61 /** 62 * Query parameter used to limit the number of call logs returned. 63 * <p> 64 * TYPE: integer 65 */ 66 public static final String LIMIT_PARAM_KEY = "limit"; 67 68 /** 69 * Query parameter used to specify the starting record to return. 70 * <p> 71 * TYPE: integer 72 */ 73 public static final String OFFSET_PARAM_KEY = "offset"; 74 75 /** 76 * An optional URI parameter which instructs the provider to allow the operation to be 77 * applied to voicemail records as well. 78 * <p> 79 * TYPE: Boolean 80 * <p> 81 * Using this parameter with a value of {@code true} will result in a security error if the 82 * calling package does not have appropriate permissions to access voicemails. 83 * 84 * @hide 85 */ 86 public static final String ALLOW_VOICEMAILS_PARAM_KEY = "allow_voicemails"; 87 88 /** 89 * Content uri with {@link #ALLOW_VOICEMAILS_PARAM_KEY} set. This can directly be used to 90 * access call log entries that includes voicemail records. 91 * 92 * @hide 93 */ 94 public static final Uri CONTENT_URI_WITH_VOICEMAIL = CONTENT_URI.buildUpon() 95 .appendQueryParameter(ALLOW_VOICEMAILS_PARAM_KEY, "true") 96 .build(); 97 98 /** 99 * The default sort order for this table 100 */ 101 public static final String DEFAULT_SORT_ORDER = "date DESC"; 102 103 /** 104 * The MIME type of {@link #CONTENT_URI} and {@link #CONTENT_FILTER_URI} 105 * providing a directory of calls. 106 */ 107 public static final String CONTENT_TYPE = "vnd.android.cursor.dir/calls"; 108 109 /** 110 * The MIME type of a {@link #CONTENT_URI} sub-directory of a single 111 * call. 112 */ 113 public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/calls"; 114 115 /** 116 * The type of the call (incoming, outgoing or missed). 117 * <P>Type: INTEGER (int)</P> 118 */ 119 public static final String TYPE = "type"; 120 121 /** Call log type for incoming calls. */ 122 public static final int INCOMING_TYPE = 1; 123 /** Call log type for outgoing calls. */ 124 public static final int OUTGOING_TYPE = 2; 125 /** Call log type for missed calls. */ 126 public static final int MISSED_TYPE = 3; 127 /** 128 * Call log type for voicemails. 129 * @hide 130 */ 131 public static final int VOICEMAIL_TYPE = 4; 132 133 /** 134 * The phone number as the user entered it. 135 * <P>Type: TEXT</P> 136 */ 137 public static final String NUMBER = "number"; 138 139 /** 140 * The ISO 3166-1 two letters country code of the country where the 141 * user received or made the call. 142 * <P> 143 * Type: TEXT 144 * </P> 145 * 146 * @hide 147 */ 148 public static final String COUNTRY_ISO = "countryiso"; 149 150 /** 151 * The date the call occured, in milliseconds since the epoch 152 * <P>Type: INTEGER (long)</P> 153 */ 154 public static final String DATE = "date"; 155 156 /** 157 * The duration of the call in seconds 158 * <P>Type: INTEGER (long)</P> 159 */ 160 public static final String DURATION = "duration"; 161 162 /** 163 * Whether or not the call has been acknowledged 164 * <P>Type: INTEGER (boolean)</P> 165 */ 166 public static final String NEW = "new"; 167 168 /** 169 * The cached name associated with the phone number, if it exists. 170 * This value is not guaranteed to be current, if the contact information 171 * associated with this number has changed. 172 * <P>Type: TEXT</P> 173 */ 174 public static final String CACHED_NAME = "name"; 175 176 /** 177 * The cached number type (Home, Work, etc) associated with the 178 * phone number, if it exists. 179 * This value is not guaranteed to be current, if the contact information 180 * associated with this number has changed. 181 * <P>Type: INTEGER</P> 182 */ 183 public static final String CACHED_NUMBER_TYPE = "numbertype"; 184 185 /** 186 * The cached number label, for a custom number type, associated with the 187 * phone number, if it exists. 188 * This value is not guaranteed to be current, if the contact information 189 * associated with this number has changed. 190 * <P>Type: TEXT</P> 191 */ 192 public static final String CACHED_NUMBER_LABEL = "numberlabel"; 193 194 /** 195 * URI of the voicemail entry. Populated only for {@link #VOICEMAIL_TYPE}. 196 * <P>Type: TEXT</P> 197 * @hide 198 */ 199 public static final String VOICEMAIL_URI = "voicemail_uri"; 200 201 /** 202 * Whether this item has been read or otherwise consumed by the user. 203 * <p> 204 * Unlike the {@link #NEW} field, which requires the user to have acknowledged the 205 * existence of the entry, this implies the user has interacted with the entry. 206 * <P>Type: INTEGER (boolean)</P> 207 */ 208 public static final String IS_READ = "is_read"; 209 210 /** 211 * A geocoded location for the number associated with this call. 212 * <p> 213 * The string represents a city, state, or country associated with the number. 214 * <P>Type: TEXT</P> 215 * @hide 216 */ 217 public static final String GEOCODED_LOCATION = "geocoded_location"; 218 219 /** 220 * The cached URI to look up the contact associated with the phone number, if it exists. 221 * This value is not guaranteed to be current, if the contact information 222 * associated with this number has changed. 223 * <P>Type: TEXT</P> 224 * @hide 225 */ 226 public static final String CACHED_LOOKUP_URI = "lookup_uri"; 227 228 /** 229 * The cached phone number of the contact which matches this entry, if it exists. 230 * This value is not guaranteed to be current, if the contact information 231 * associated with this number has changed. 232 * <P>Type: TEXT</P> 233 * @hide 234 */ 235 public static final String CACHED_MATCHED_NUMBER = "matched_number"; 236 237 /** 238 * The cached normalized version of the phone number, if it exists. 239 * This value is not guaranteed to be current, if the contact information 240 * associated with this number has changed. 241 * <P>Type: TEXT</P> 242 * @hide 243 */ 244 public static final String CACHED_NORMALIZED_NUMBER = "normalized_number"; 245 246 /** 247 * The cached photo id of the picture associated with the phone number, if it exists. 248 * This value is not guaranteed to be current, if the contact information 249 * associated with this number has changed. 250 * <P>Type: INTEGER (long)</P> 251 * @hide 252 */ 253 public static final String CACHED_PHOTO_ID = "photo_id"; 254 255 /** 256 * The cached formatted phone number. 257 * This value is not guaranteed to be present. 258 * <P>Type: TEXT</P> 259 * @hide 260 */ 261 public static final String CACHED_FORMATTED_NUMBER = "formatted_number"; 262 263 /** 264 * Adds a call to the call log. 265 * 266 * @param ci the CallerInfo object to get the target contact from. Can be null 267 * if the contact is unknown. 268 * @param context the context used to get the ContentResolver 269 * @param number the phone number to be added to the calls db 270 * @param presentation the number presenting rules set by the network for 271 * "allowed", "payphone", "restricted" or "unknown" 272 * @param callType enumerated values for "incoming", "outgoing", or "missed" 273 * @param start time stamp for the call in milliseconds 274 * @param duration call duration in seconds 275 * 276 * {@hide} 277 */ addCall(CallerInfo ci, Context context, String number, int presentation, int callType, long start, int duration)278 public static Uri addCall(CallerInfo ci, Context context, String number, 279 int presentation, int callType, long start, int duration) { 280 final ContentResolver resolver = context.getContentResolver(); 281 282 // If this is a private number then set the number to Private, otherwise check 283 // if the number field is empty and set the number to Unavailable 284 if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) { 285 number = CallerInfo.PRIVATE_NUMBER; 286 if (ci != null) ci.name = ""; 287 } else if (presentation == PhoneConstants.PRESENTATION_PAYPHONE) { 288 number = CallerInfo.PAYPHONE_NUMBER; 289 if (ci != null) ci.name = ""; 290 } else if (TextUtils.isEmpty(number) 291 || presentation == PhoneConstants.PRESENTATION_UNKNOWN) { 292 number = CallerInfo.UNKNOWN_NUMBER; 293 if (ci != null) ci.name = ""; 294 } 295 296 ContentValues values = new ContentValues(5); 297 298 values.put(NUMBER, number); 299 values.put(TYPE, Integer.valueOf(callType)); 300 values.put(DATE, Long.valueOf(start)); 301 values.put(DURATION, Long.valueOf(duration)); 302 values.put(NEW, Integer.valueOf(1)); 303 if (callType == MISSED_TYPE) { 304 values.put(IS_READ, Integer.valueOf(0)); 305 } 306 if (ci != null) { 307 values.put(CACHED_NAME, ci.name); 308 values.put(CACHED_NUMBER_TYPE, ci.numberType); 309 values.put(CACHED_NUMBER_LABEL, ci.numberLabel); 310 } 311 312 if ((ci != null) && (ci.person_id > 0)) { 313 // Update usage information for the number associated with the contact ID. 314 // We need to use both the number and the ID for obtaining a data ID since other 315 // contacts may have the same number. 316 317 final Cursor cursor; 318 319 // We should prefer normalized one (probably coming from 320 // Phone.NORMALIZED_NUMBER column) first. If it isn't available try others. 321 if (ci.normalizedNumber != null) { 322 final String normalizedPhoneNumber = ci.normalizedNumber; 323 cursor = resolver.query(Phone.CONTENT_URI, 324 new String[] { Phone._ID }, 325 Phone.CONTACT_ID + " =? AND " + Phone.NORMALIZED_NUMBER + " =?", 326 new String[] { String.valueOf(ci.person_id), normalizedPhoneNumber}, 327 null); 328 } else { 329 final String phoneNumber = ci.phoneNumber != null ? ci.phoneNumber : number; 330 cursor = resolver.query( 331 Uri.withAppendedPath(Callable.CONTENT_FILTER_URI, 332 Uri.encode(phoneNumber)), 333 new String[] { Phone._ID }, 334 Phone.CONTACT_ID + " =?", 335 new String[] { String.valueOf(ci.person_id) }, 336 null); 337 } 338 339 if (cursor != null) { 340 try { 341 if (cursor.getCount() > 0 && cursor.moveToFirst()) { 342 final Uri feedbackUri = DataUsageFeedback.FEEDBACK_URI.buildUpon() 343 .appendPath(cursor.getString(0)) 344 .appendQueryParameter(DataUsageFeedback.USAGE_TYPE, 345 DataUsageFeedback.USAGE_TYPE_CALL) 346 .build(); 347 resolver.update(feedbackUri, new ContentValues(), null, null); 348 } 349 } finally { 350 cursor.close(); 351 } 352 } 353 } 354 355 Uri result = resolver.insert(CONTENT_URI, values); 356 357 removeExpiredEntries(context); 358 359 return result; 360 } 361 362 /** 363 * Query the call log database for the last dialed number. 364 * @param context Used to get the content resolver. 365 * @return The last phone number dialed (outgoing) or an empty 366 * string if none exist yet. 367 */ getLastOutgoingCall(Context context)368 public static String getLastOutgoingCall(Context context) { 369 final ContentResolver resolver = context.getContentResolver(); 370 Cursor c = null; 371 try { 372 c = resolver.query( 373 CONTENT_URI, 374 new String[] {NUMBER}, 375 TYPE + " = " + OUTGOING_TYPE, 376 null, 377 DEFAULT_SORT_ORDER + " LIMIT 1"); 378 if (c == null || !c.moveToFirst()) { 379 return ""; 380 } 381 return c.getString(0); 382 } finally { 383 if (c != null) c.close(); 384 } 385 } 386 removeExpiredEntries(Context context)387 private static void removeExpiredEntries(Context context) { 388 final ContentResolver resolver = context.getContentResolver(); 389 resolver.delete(CONTENT_URI, "_id IN " + 390 "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER 391 + " LIMIT -1 OFFSET 500)", null); 392 } 393 } 394 } 395