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