• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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