CallLog.java revision 8c8a062f521d39ceecc99e8fc6cef9c60b773da5
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.ContentResolver;
21import android.content.ContentValues;
22import android.content.Context;
23import android.content.Intent;
24import android.database.Cursor;
25import android.net.Uri;
26import android.provider.ContactsContract.CommonDataKinds.Callable;
27import android.provider.ContactsContract.CommonDataKinds.Phone;
28import android.provider.ContactsContract.DataUsageFeedback;
29import android.telecomm.PhoneAccountHandle;
30import android.text.TextUtils;
31
32import com.android.internal.telephony.CallerInfo;
33import com.android.internal.telephony.PhoneConstants;
34
35/**
36 * The CallLog provider contains information about placed and received calls.
37 */
38public class CallLog {
39    public static final String AUTHORITY = "call_log";
40
41    /**
42     * The content:// style URL for this provider
43     */
44    public static final Uri CONTENT_URI =
45        Uri.parse("content://" + AUTHORITY);
46
47    /**
48     * Contains the recent calls.
49     */
50    public static class Calls implements BaseColumns {
51        /**
52         * The content:// style URL for this table
53         */
54        public static final Uri CONTENT_URI =
55                Uri.parse("content://call_log/calls");
56
57        /**
58         * The content:// style URL for filtering this table on phone numbers
59         */
60        public static final Uri CONTENT_FILTER_URI =
61                Uri.parse("content://call_log/calls/filter");
62
63        /**
64         * Query parameter used to limit the number of call logs returned.
65         * <p>
66         * TYPE: integer
67         */
68        public static final String LIMIT_PARAM_KEY = "limit";
69
70        /**
71         * Query parameter used to specify the starting record to return.
72         * <p>
73         * TYPE: integer
74         */
75        public static final String OFFSET_PARAM_KEY = "offset";
76
77        /**
78         * An optional URI parameter which instructs the provider to allow the operation to be
79         * applied to voicemail records as well.
80         * <p>
81         * TYPE: Boolean
82         * <p>
83         * Using this parameter with a value of {@code true} will result in a security error if the
84         * calling package does not have appropriate permissions to access voicemails.
85         *
86         * @hide
87         */
88        public static final String ALLOW_VOICEMAILS_PARAM_KEY = "allow_voicemails";
89
90        /**
91         * An optional extra used with {@link #CONTENT_TYPE Calls.CONTENT_TYPE} and
92         * {@link Intent#ACTION_VIEW} to specify that the presented list of calls should be
93         * filtered for a particular call type.
94         *
95         * Applications implementing a call log UI should check for this extra, and display a
96         * filtered list of calls based on the specified call type. If not applicable within the
97         * application's UI, it should be silently ignored.
98         *
99         * <p>
100         * The following example brings up the call log, showing only missed calls.
101         * <pre>
102         * Intent intent = new Intent(Intent.ACTION_VIEW);
103         * intent.setType(CallLog.Calls.CONTENT_TYPE);
104         * intent.putExtra(CallLog.Calls.EXTRA_CALL_TYPE_FILTER, CallLog.Calls.MISSED_TYPE);
105         * startActivity(intent);
106         * </pre>
107         * </p>
108         */
109        public static final String EXTRA_CALL_TYPE_FILTER = "extra_call_type_filter";
110
111        /**
112         * Content uri used to access call log entries, including voicemail records. You must have
113         * the READ_CALL_LOG and WRITE_CALL_LOG permissions to read and write to the call log.
114         */
115        public static final Uri CONTENT_URI_WITH_VOICEMAIL = CONTENT_URI.buildUpon()
116                .appendQueryParameter(ALLOW_VOICEMAILS_PARAM_KEY, "true")
117                .build();
118
119        /**
120         * The default sort order for this table
121         */
122        public static final String DEFAULT_SORT_ORDER = "date DESC";
123
124        /**
125         * The MIME type of {@link #CONTENT_URI} and {@link #CONTENT_FILTER_URI}
126         * providing a directory of calls.
127         */
128        public static final String CONTENT_TYPE = "vnd.android.cursor.dir/calls";
129
130        /**
131         * The MIME type of a {@link #CONTENT_URI} sub-directory of a single
132         * call.
133         */
134        public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/calls";
135
136        /**
137         * The type of the call (incoming, outgoing or missed).
138         * <P>Type: INTEGER (int)</P>
139         */
140        public static final String TYPE = "type";
141
142        /** Call log type for incoming calls. */
143        public static final int INCOMING_TYPE = 1;
144        /** Call log type for outgoing calls. */
145        public static final int OUTGOING_TYPE = 2;
146        /** Call log type for missed calls. */
147        public static final int MISSED_TYPE = 3;
148        /** Call log type for voicemails. */
149        public static final int VOICEMAIL_TYPE = 4;
150
151        /**
152         * Bit-mask describing features of the call (e.g. video).
153         *
154         * <P>Type: INTEGER (int)</P>
155         */
156        public static final String FEATURES = "features";
157
158        /** Call had no associated features (e.g. voice-only). */
159        public static final int FEATURES_NONE = 0x0;
160        /** Call had video. */
161        public static final int FEATURES_VIDEO = 0x1;
162
163        /**
164         * The phone number as the user entered it.
165         * <P>Type: TEXT</P>
166         */
167        public static final String NUMBER = "number";
168
169        /**
170         * The number presenting rules set by the network.
171         *
172         * <p>
173         * Allowed values:
174         * <ul>
175         * <li>{@link #PRESENTATION_ALLOWED}</li>
176         * <li>{@link #PRESENTATION_RESTRICTED}</li>
177         * <li>{@link #PRESENTATION_UNKNOWN}</li>
178         * <li>{@link #PRESENTATION_PAYPHONE}</li>
179         * </ul>
180         * </p>
181         *
182         * <P>Type: INTEGER</P>
183         */
184        public static final String NUMBER_PRESENTATION = "presentation";
185
186        /** Number is allowed to display for caller id. */
187        public static final int PRESENTATION_ALLOWED = 1;
188        /** Number is blocked by user. */
189        public static final int PRESENTATION_RESTRICTED = 2;
190        /** Number is not specified or unknown by network. */
191        public static final int PRESENTATION_UNKNOWN = 3;
192        /** Number is a pay phone. */
193        public static final int PRESENTATION_PAYPHONE = 4;
194
195        /**
196         * The ISO 3166-1 two letters country code of the country where the
197         * user received or made the call.
198         * <P>
199         * Type: TEXT
200         * </P>
201         */
202        public static final String COUNTRY_ISO = "countryiso";
203
204        /**
205         * The date the call occured, in milliseconds since the epoch
206         * <P>Type: INTEGER (long)</P>
207         */
208        public static final String DATE = "date";
209
210        /**
211         * The duration of the call in seconds
212         * <P>Type: INTEGER (long)</P>
213         */
214        public static final String DURATION = "duration";
215
216        /**
217         * The data usage of the call in bytes.
218         * <P>Type: INTEGER (long)</P>
219         */
220        public static final String DATA_USAGE = "data_usage";
221
222        /**
223         * Whether or not the call has been acknowledged
224         * <P>Type: INTEGER (boolean)</P>
225         */
226        public static final String NEW = "new";
227
228        /**
229         * The cached name associated with the phone number, 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         */
234        public static final String CACHED_NAME = "name";
235
236        /**
237         * The cached number type (Home, Work, etc) associated with the
238         * 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: INTEGER</P>
242         */
243        public static final String CACHED_NUMBER_TYPE = "numbertype";
244
245        /**
246         * The cached number label, for a custom number type, associated with the
247         * 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: TEXT</P>
251         */
252        public static final String CACHED_NUMBER_LABEL = "numberlabel";
253
254        /**
255         * URI of the voicemail entry. Populated only for {@link #VOICEMAIL_TYPE}.
256         * <P>Type: TEXT</P>
257         */
258        public static final String VOICEMAIL_URI = "voicemail_uri";
259
260        /**
261         * Whether this item has been read or otherwise consumed by the user.
262         * <p>
263         * Unlike the {@link #NEW} field, which requires the user to have acknowledged the
264         * existence of the entry, this implies the user has interacted with the entry.
265         * <P>Type: INTEGER (boolean)</P>
266         */
267        public static final String IS_READ = "is_read";
268
269        /**
270         * A geocoded location for the number associated with this call.
271         * <p>
272         * The string represents a city, state, or country associated with the number.
273         * <P>Type: TEXT</P>
274         */
275        public static final String GEOCODED_LOCATION = "geocoded_location";
276
277        /**
278         * The cached URI to look up the contact associated with the phone number, if it exists.
279         * This value may not be current if the contact information associated with this number
280         * has changed.
281         * <P>Type: TEXT</P>
282         */
283        public static final String CACHED_LOOKUP_URI = "lookup_uri";
284
285        /**
286         * The cached phone number of the contact which matches this entry, if it exists.
287         * This value may not be current if the contact information associated with this number
288         * has changed.
289         * <P>Type: TEXT</P>
290         */
291        public static final String CACHED_MATCHED_NUMBER = "matched_number";
292
293        /**
294         * The cached normalized(E164) version of 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_NORMALIZED_NUMBER = "normalized_number";
300
301        /**
302         * The cached photo id of the picture associated with the phone number, if it exists.
303         * This value may not be current if the contact information associated with this number
304         * has changed.
305         * <P>Type: INTEGER (long)</P>
306         */
307        public static final String CACHED_PHOTO_ID = "photo_id";
308
309        /**
310         * The cached phone number, formatted with formatting rules based on the country the
311         * user was in when the call was made or received.
312         * This value is not guaranteed to be present, and may not be current if the contact
313         * information associated with this number
314         * has changed.
315         * <P>Type: TEXT</P>
316         */
317        public static final String CACHED_FORMATTED_NUMBER = "formatted_number";
318
319        // Note: PHONE_ACCOUNT_* constant values are "subscription_*" due to a historic naming
320        // that was encoded into call log databases.
321
322        /**
323         * The component name of the account in string form.
324         * <P>Type: TEXT</P>
325         */
326        public static final String PHONE_ACCOUNT_COMPONENT_NAME = "subscription_component_name";
327
328        /**
329         * The identifier of a account that is unique to a specified component.
330         * <P>Type: TEXT</P>
331         */
332        public static final String PHONE_ACCOUNT_ID = "subscription_id";
333
334        /**
335         * Adds a call to the call log.
336         *
337         * @param ci the CallerInfo object to get the target contact from.  Can be null
338         * if the contact is unknown.
339         * @param context the context used to get the ContentResolver
340         * @param number the phone number to be added to the calls db
341         * @param presentation enum value from PhoneConstants.PRESENTATION_xxx, which
342         *        is set by the network and denotes the number presenting rules for
343         *        "allowed", "payphone", "restricted" or "unknown"
344         * @param callType enumerated values for "incoming", "outgoing", or "missed"
345         * @param features features of the call (e.g. Video).
346         * @param accountHandle The accountHandle object identifying the provider of the call
347         * @param start time stamp for the call in milliseconds
348         * @param duration call duration in seconds
349         * @param dataUsage data usage for the call in bytes, null if data usage was not tracked for
350         *                  the call.
351         *
352         * {@hide}
353         */
354        public static Uri addCall(CallerInfo ci, Context context, String number,
355                int presentation, int callType, int features, PhoneAccountHandle accountHandle,
356                long start, int duration, Long dataUsage) {
357            final ContentResolver resolver = context.getContentResolver();
358            int numberPresentation = PRESENTATION_ALLOWED;
359
360            // Remap network specified number presentation types
361            // PhoneConstants.PRESENTATION_xxx to calllog number presentation types
362            // Calls.PRESENTATION_xxx, in order to insulate the persistent calllog
363            // from any future radio changes.
364            // If the number field is empty set the presentation type to Unknown.
365            if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) {
366                numberPresentation = PRESENTATION_RESTRICTED;
367            } else if (presentation == PhoneConstants.PRESENTATION_PAYPHONE) {
368                numberPresentation = PRESENTATION_PAYPHONE;
369            } else if (TextUtils.isEmpty(number)
370                    || presentation == PhoneConstants.PRESENTATION_UNKNOWN) {
371                numberPresentation = PRESENTATION_UNKNOWN;
372            }
373            if (numberPresentation != PRESENTATION_ALLOWED) {
374                number = "";
375                if (ci != null) {
376                    ci.name = "";
377                }
378            }
379
380            // accountHandle information
381            String accountComponentString = null;
382            String accountId = null;
383            if (accountHandle != null) {
384                accountComponentString = accountHandle.getComponentName().flattenToString();
385                accountId = accountHandle.getId();
386            }
387
388            ContentValues values = new ContentValues(6);
389
390            values.put(NUMBER, number);
391            values.put(NUMBER_PRESENTATION, Integer.valueOf(numberPresentation));
392            values.put(TYPE, Integer.valueOf(callType));
393            values.put(FEATURES, features);
394            values.put(DATE, Long.valueOf(start));
395            values.put(DURATION, Long.valueOf(duration));
396            if (dataUsage != null) {
397                values.put(DATA_USAGE, dataUsage);
398            }
399            values.put(PHONE_ACCOUNT_COMPONENT_NAME, accountComponentString);
400            values.put(PHONE_ACCOUNT_ID, accountId);
401            values.put(NEW, Integer.valueOf(1));
402            if (callType == MISSED_TYPE) {
403                values.put(IS_READ, Integer.valueOf(0));
404            }
405            if (ci != null) {
406                values.put(CACHED_NAME, ci.name);
407                values.put(CACHED_NUMBER_TYPE, ci.numberType);
408                values.put(CACHED_NUMBER_LABEL, ci.numberLabel);
409            }
410
411            if ((ci != null) && (ci.contactIdOrZero > 0)) {
412                // Update usage information for the number associated with the contact ID.
413                // We need to use both the number and the ID for obtaining a data ID since other
414                // contacts may have the same number.
415
416                final Cursor cursor;
417
418                // We should prefer normalized one (probably coming from
419                // Phone.NORMALIZED_NUMBER column) first. If it isn't available try others.
420                if (ci.normalizedNumber != null) {
421                    final String normalizedPhoneNumber = ci.normalizedNumber;
422                    cursor = resolver.query(Phone.CONTENT_URI,
423                            new String[] { Phone._ID },
424                            Phone.CONTACT_ID + " =? AND " + Phone.NORMALIZED_NUMBER + " =?",
425                            new String[] { String.valueOf(ci.contactIdOrZero),
426                                    normalizedPhoneNumber},
427                            null);
428                } else {
429                    final String phoneNumber = ci.phoneNumber != null ? ci.phoneNumber : number;
430                    cursor = resolver.query(
431                            Uri.withAppendedPath(Callable.CONTENT_FILTER_URI,
432                                    Uri.encode(phoneNumber)),
433                            new String[] { Phone._ID },
434                            Phone.CONTACT_ID + " =?",
435                            new String[] { String.valueOf(ci.contactIdOrZero) },
436                            null);
437                }
438
439                if (cursor != null) {
440                    try {
441                        if (cursor.getCount() > 0 && cursor.moveToFirst()) {
442                            final Uri feedbackUri = DataUsageFeedback.FEEDBACK_URI.buildUpon()
443                                    .appendPath(cursor.getString(0))
444                                    .appendQueryParameter(DataUsageFeedback.USAGE_TYPE,
445                                                DataUsageFeedback.USAGE_TYPE_CALL)
446                                    .build();
447                            resolver.update(feedbackUri, new ContentValues(), null, null);
448                        }
449                    } finally {
450                        cursor.close();
451                    }
452                }
453            }
454
455            Uri result = resolver.insert(CONTENT_URI, values);
456
457            removeExpiredEntries(context);
458
459            return result;
460        }
461
462        /**
463         * Query the call log database for the last dialed number.
464         * @param context Used to get the content resolver.
465         * @return The last phone number dialed (outgoing) or an empty
466         * string if none exist yet.
467         */
468        public static String getLastOutgoingCall(Context context) {
469            final ContentResolver resolver = context.getContentResolver();
470            Cursor c = null;
471            try {
472                c = resolver.query(
473                    CONTENT_URI,
474                    new String[] {NUMBER},
475                    TYPE + " = " + OUTGOING_TYPE,
476                    null,
477                    DEFAULT_SORT_ORDER + " LIMIT 1");
478                if (c == null || !c.moveToFirst()) {
479                    return "";
480                }
481                return c.getString(0);
482            } finally {
483                if (c != null) c.close();
484            }
485        }
486
487        private static void removeExpiredEntries(Context context) {
488            final ContentResolver resolver = context.getContentResolver();
489            resolver.delete(CONTENT_URI, "_id IN " +
490                    "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER
491                    + " LIMIT -1 OFFSET 500)", null);
492        }
493    }
494}
495