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