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