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 com.android.internal.telephony.CallerInfo; 21 import com.android.internal.telephony.Connection; 22 23 import android.content.ContentResolver; 24 import android.content.ContentValues; 25 import android.content.Context; 26 import android.database.Cursor; 27 import android.net.Uri; 28 import android.provider.ContactsContract.CommonDataKinds.Phone; 29 import android.provider.ContactsContract.DataUsageFeedback; 30 import android.text.TextUtils; 31 32 /** 33 * The CallLog provider contains information about placed and received calls. 34 */ 35 public 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 */ addCall(CallerInfo ci, Context context, String number, int presentation, int callType, long start, int duration)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 */ getLastOutgoingCall(Context context)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 removeExpiredEntries(Context context)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