CallLog.java revision 2d1ee982276f5dbd0e035778b731b48e4ef34515
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.location.Country;
28import android.location.CountryDetector;
29import android.net.Uri;
30import android.os.UserHandle;
31import android.os.UserManager;
32import android.provider.ContactsContract.CommonDataKinds.Callable;
33import android.provider.ContactsContract.CommonDataKinds.Phone;
34import android.provider.ContactsContract.Data;
35import android.provider.ContactsContract.DataUsageFeedback;
36import android.telecom.PhoneAccountHandle;
37import android.telephony.PhoneNumberUtils;
38import android.text.TextUtils;
39
40import com.android.internal.telephony.CallerInfo;
41import com.android.internal.telephony.PhoneConstants;
42
43import java.util.List;
44
45/**
46 * The CallLog provider contains information about placed and received calls.
47 */
48public 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         */
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            // FIXME using -1 as subId instead of SubscriptionManager.INVALID_SUB_ID
391            return addCall(ci, context, number, presentation, callType, features, accountHandle,
392                    start, duration, dataUsage, false);
393        }
394
395
396        /**
397         * Adds a call to the call log.
398         *
399         * @param ci the CallerInfo object to get the target contact from.  Can be null
400         * if the contact is unknown.
401         * @param context the context used to get the ContentResolver
402         * @param number the phone number to be added to the calls db
403         * @param presentation enum value from PhoneConstants.PRESENTATION_xxx, which
404         *        is set by the network and denotes the number presenting rules for
405         *        "allowed", "payphone", "restricted" or "unknown"
406         * @param callType enumerated values for "incoming", "outgoing", or "missed"
407         * @param features features of the call (e.g. Video).
408         * @param accountHandle The accountHandle object identifying the provider of the call
409         * @param start time stamp for the call in milliseconds
410         * @param duration call duration in seconds
411         * @param dataUsage data usage for the call in bytes, null if data usage was not tracked for
412         *                  the call.
413         * @param addForAllUsers If true, the call is added to the call log of all currently
414         *        running users. The caller must have the MANAGE_USERS permission if this is true.
415         *
416         * @result The URI of the call log entry belonging to the user that made or received this
417         *        call.
418         * {@hide}
419         */
420        public static Uri addCall(CallerInfo ci, Context context, String number,
421                int presentation, int callType, int features, PhoneAccountHandle accountHandle,
422                long start, int duration, Long dataUsage, boolean addForAllUsers) {
423            final ContentResolver resolver = context.getContentResolver();
424            int numberPresentation = PRESENTATION_ALLOWED;
425
426            // Remap network specified number presentation types
427            // PhoneConstants.PRESENTATION_xxx to calllog number presentation types
428            // Calls.PRESENTATION_xxx, in order to insulate the persistent calllog
429            // from any future radio changes.
430            // If the number field is empty set the presentation type to Unknown.
431            if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) {
432                numberPresentation = PRESENTATION_RESTRICTED;
433            } else if (presentation == PhoneConstants.PRESENTATION_PAYPHONE) {
434                numberPresentation = PRESENTATION_PAYPHONE;
435            } else if (TextUtils.isEmpty(number)
436                    || presentation == PhoneConstants.PRESENTATION_UNKNOWN) {
437                numberPresentation = PRESENTATION_UNKNOWN;
438            }
439            if (numberPresentation != PRESENTATION_ALLOWED) {
440                number = "";
441                if (ci != null) {
442                    ci.name = "";
443                }
444            }
445
446            // accountHandle information
447            String accountComponentString = null;
448            String accountId = null;
449            if (accountHandle != null) {
450                accountComponentString = accountHandle.getComponentName().flattenToString();
451                accountId = accountHandle.getId();
452            }
453
454            ContentValues values = new ContentValues(6);
455
456            values.put(NUMBER, number);
457            values.put(NUMBER_PRESENTATION, Integer.valueOf(numberPresentation));
458            values.put(TYPE, Integer.valueOf(callType));
459            values.put(FEATURES, features);
460            values.put(DATE, Long.valueOf(start));
461            values.put(DURATION, Long.valueOf(duration));
462            if (dataUsage != null) {
463                values.put(DATA_USAGE, dataUsage);
464            }
465            values.put(PHONE_ACCOUNT_COMPONENT_NAME, accountComponentString);
466            values.put(PHONE_ACCOUNT_ID, accountId);
467            values.put(NEW, Integer.valueOf(1));
468
469            if (callType == MISSED_TYPE) {
470                values.put(IS_READ, Integer.valueOf(0));
471            }
472            if (ci != null) {
473                values.put(CACHED_NAME, ci.name);
474                values.put(CACHED_NUMBER_TYPE, ci.numberType);
475                values.put(CACHED_NUMBER_LABEL, ci.numberLabel);
476            }
477
478            if ((ci != null) && (ci.contactIdOrZero > 0)) {
479                // Update usage information for the number associated with the contact ID.
480                // We need to use both the number and the ID for obtaining a data ID since other
481                // contacts may have the same number.
482
483                final Cursor cursor;
484
485                // We should prefer normalized one (probably coming from
486                // Phone.NORMALIZED_NUMBER column) first. If it isn't available try others.
487                if (ci.normalizedNumber != null) {
488                    final String normalizedPhoneNumber = ci.normalizedNumber;
489                    cursor = resolver.query(Phone.CONTENT_URI,
490                            new String[] { Phone._ID },
491                            Phone.CONTACT_ID + " =? AND " + Phone.NORMALIZED_NUMBER + " =?",
492                            new String[] { String.valueOf(ci.contactIdOrZero),
493                                    normalizedPhoneNumber},
494                            null);
495                } else {
496                    final String phoneNumber = ci.phoneNumber != null ? ci.phoneNumber : number;
497                    cursor = resolver.query(
498                            Uri.withAppendedPath(Callable.CONTENT_FILTER_URI,
499                                    Uri.encode(phoneNumber)),
500                            new String[] { Phone._ID },
501                            Phone.CONTACT_ID + " =?",
502                            new String[] { String.valueOf(ci.contactIdOrZero) },
503                            null);
504                }
505
506                if (cursor != null) {
507                    try {
508                        if (cursor.getCount() > 0 && cursor.moveToFirst()) {
509                            final String dataId = cursor.getString(0);
510                            updateDataUsageStatForData(resolver, dataId);
511                            if (duration >= MIN_DURATION_FOR_NORMALIZED_NUMBER_UPDATE_MS
512                                    && callType == Calls.OUTGOING_TYPE
513                                    && TextUtils.isEmpty(ci.normalizedNumber)) {
514                                updateNormalizedNumber(context, resolver, dataId, number);
515                            }
516                        }
517                    } finally {
518                        cursor.close();
519                    }
520                }
521            }
522
523            Uri result = null;
524
525            if (addForAllUsers) {
526                // Insert the entry for all currently running users, in order to trigger any
527                // ContentObservers currently set on the call log.
528                final UserManager userManager = (UserManager) context.getSystemService(
529                        Context.USER_SERVICE);
530                List<UserInfo> users = userManager.getUsers(true);
531                final int currentUserId = userManager.getUserHandle();
532                final int count = users.size();
533                for (int i = 0; i < count; i++) {
534                    final UserInfo user = users.get(i);
535                    final UserHandle userHandle = user.getUserHandle();
536                    if (userManager.isUserRunning(userHandle)
537                            && !userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,
538                                    userHandle)
539                            && !user.isManagedProfile()) {
540                        Uri uri = addEntryAndRemoveExpiredEntries(context,
541                                ContentProvider.maybeAddUserId(CONTENT_URI, user.id), values);
542                        if (user.id == currentUserId) {
543                            result = uri;
544                        }
545                    }
546                }
547            } else {
548                result = addEntryAndRemoveExpiredEntries(context, CONTENT_URI, values);
549            }
550
551            return result;
552        }
553
554        /**
555         * Query the call log database for the last dialed number.
556         * @param context Used to get the content resolver.
557         * @return The last phone number dialed (outgoing) or an empty
558         * string if none exist yet.
559         */
560        public static String getLastOutgoingCall(Context context) {
561            final ContentResolver resolver = context.getContentResolver();
562            Cursor c = null;
563            try {
564                c = resolver.query(
565                    CONTENT_URI,
566                    new String[] {NUMBER},
567                    TYPE + " = " + OUTGOING_TYPE,
568                    null,
569                    DEFAULT_SORT_ORDER + " LIMIT 1");
570                if (c == null || !c.moveToFirst()) {
571                    return "";
572                }
573                return c.getString(0);
574            } finally {
575                if (c != null) c.close();
576            }
577        }
578
579        private static Uri addEntryAndRemoveExpiredEntries(Context context, Uri uri,
580                ContentValues values) {
581            final ContentResolver resolver = context.getContentResolver();
582            Uri result = resolver.insert(uri, values);
583            resolver.delete(uri, "_id IN " +
584                    "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER
585                    + " LIMIT -1 OFFSET 500)", null);
586            return result;
587        }
588
589        private static void updateDataUsageStatForData(ContentResolver resolver, String dataId) {
590            final Uri feedbackUri = DataUsageFeedback.FEEDBACK_URI.buildUpon()
591                    .appendPath(dataId)
592                    .appendQueryParameter(DataUsageFeedback.USAGE_TYPE,
593                                DataUsageFeedback.USAGE_TYPE_CALL)
594                    .build();
595            resolver.update(feedbackUri, new ContentValues(), null, null);
596        }
597
598        /*
599         * Update the normalized phone number for the given dataId in the ContactsProvider, based
600         * on the user's current country.
601         */
602        private static void updateNormalizedNumber(Context context, ContentResolver resolver,
603                String dataId, String number) {
604            if (TextUtils.isEmpty(number) || TextUtils.isEmpty(dataId)) {
605                return;
606            }
607            final String countryIso = getCurrentCountryIso(context);
608            if (TextUtils.isEmpty(countryIso)) {
609                return;
610            }
611            final String normalizedNumber = PhoneNumberUtils.formatNumberToE164(number,
612                    getCurrentCountryIso(context));
613            if (TextUtils.isEmpty(normalizedNumber)) {
614                return;
615            }
616            final ContentValues values = new ContentValues();
617            values.put(Phone.NORMALIZED_NUMBER, normalizedNumber);
618            resolver.update(Data.CONTENT_URI, values, Data._ID + "=?", new String[] {dataId});
619        }
620
621        private static String getCurrentCountryIso(Context context) {
622            String countryIso = null;
623            final CountryDetector detector = (CountryDetector) context.getSystemService(
624                    Context.COUNTRY_DETECTOR);
625            if (detector != null) {
626                final Country country = detector.detectCountry();
627                if (country != null) {
628                    countryIso = country.getCountryIso();
629                }
630            }
631            return countryIso;
632        }
633    }
634}
635