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