/* Copyright © 2013-2014, Silent Circle, LLC. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Any redistribution, use, or modification is done solely for personal benefit and not for any commercial purpose or for monetary gain * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name Silent Circle nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SILENT CIRCLE, LLC BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package com.silentcircle.silentcontacts; import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.net.Uri; import android.provider.BaseColumns; // import android.provider.ContactsContract.CommonDataKinds.Callable; public class ScCallLog { /* * Copyright (C) 2006 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ // import com.android.internal.telephony.CallerInfo; // import com.android.internal.telephony.PhoneConstants; /** * The CallLog provider contains information about placed and received calls. */ public static final String AUTHORITY = "com.silentcircle.calls"; /** * The content:// style URL for this provider */ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY); /** * Contains the recent calls. */ public static class ScCalls implements BaseColumns { /** * The content:// style URL for this table */ public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/calls"); /** * The content:// style URL for filtering this table on phone numbers */ public static final Uri CONTENT_FILTER_URI = Uri.parse("content://" + AUTHORITY + "/calls/filter"); /** * Query parameter used to limit the number of call logs returned. * <p> * TYPE: integer */ public static final String LIMIT_PARAM_KEY = "limit"; /** * Query parameter used to specify the starting record to return. * <p> * TYPE: integer */ public static final String OFFSET_PARAM_KEY = "offset"; /** * An optional URI parameter which instructs the provider to allow the operation to be applied to voicemail records as * well. * <p> * TYPE: Boolean * <p> * Using this parameter with a value of {@code true} will result in a security error if the calling package does not have * appropriate permissions to access voicemails. * * @hide */ public static final String ALLOW_VOICEMAILS_PARAM_KEY = "allow_voicemails"; /** * The default sort order for this table */ public static final String DEFAULT_SORT_ORDER = "date DESC"; /** * The MIME type of {@link #CONTENT_URI} and {@link #CONTENT_FILTER_URI} providing a directory of calls. */ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/com.silentcircle.calls"; /** * The MIME type of a {@link #CONTENT_URI} sub-directory of a single call. */ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/com.silentcircle.calls"; /** * The type of the call (incoming, outgoing or missed). * <P> * Type: INTEGER (int) * </P> */ public static final String TYPE = "type"; /** Call log type for incoming calls. */ public static final int INCOMING_TYPE = 1; /** Call log type for outgoing calls. */ public static final int OUTGOING_TYPE = 2; /** Call log type for missed calls. */ public static final int MISSED_TYPE = 3; /** * The phone number as the user entered it. * <P> * Type: TEXT * </P> */ public static final String NUMBER = "number"; /** * The ISO 3166-1 two letters country code of the country where the user received or made the call. * <P> * Type: TEXT * </P> * * @hide */ public static final String COUNTRY_ISO = "countryiso"; /** * The date the call occured, in milliseconds since the epoch * <P> * Type: INTEGER (long) * </P> */ public static final String DATE = "date"; /** * The duration of the call in seconds * <P> * Type: INTEGER (long) * </P> */ public static final String DURATION = "duration"; /** * Whether or not the call has been acknowledged * <P> * Type: INTEGER (boolean) * </P> */ public static final String NEW = "new"; /** * The cached name associated with the phone number, if it exists. This value is not guaranteed to be current, if the * contact information associated with this number has changed. * <P> * Type: TEXT * </P> */ public static final String CACHED_NAME = "name"; /** * The cached number type (Home, Work, etc) associated with the phone number, if it exists. This value is not guaranteed * to be current, if the contact information associated with this number has changed. * <P> * Type: INTEGER * </P> */ public static final String CACHED_NUMBER_TYPE = "numbertype"; /** * The cached number label, for a custom number type, associated with the phone number, if it exists. This value is not * guaranteed to be current, if the contact information associated with this number has changed. * <P> * Type: TEXT * </P> */ public static final String CACHED_NUMBER_LABEL = "numberlabel"; /** * Whether this item has been read or otherwise consumed by the user. * <p> * Unlike the {@link #NEW} field, which requires the user to have acknowledged the existence of the entry, this implies * the user has interacted with the entry. * <P> * Type: INTEGER (boolean) * </P> */ public static final String IS_READ = "is_read"; /** * A geocoded location for the number associated with this call. * <p> * The string represents a city, state, or country associated with the number. * <P> * Type: TEXT * </P> * * @hide */ public static final String GEOCODED_LOCATION = "geocoded_location"; /** * The cached URI to look up the contact associated with the phone number, if it exists. This value is not guaranteed to * be current, if the contact information associated with this number has changed. * <P> * Type: TEXT * </P> * * @hide */ public static final String CACHED_LOOKUP_URI = "lookup_uri"; /** * The cached phone number of the contact which matches this entry, if it exists. This value is not guaranteed to be * current, if the contact information associated with this number has changed. * <P> * Type: TEXT * </P> * * @hide */ public static final String CACHED_MATCHED_NUMBER = "matched_number"; /** * The cached normalized version of the phone number, if it exists. This value is not guaranteed to be current, if the * contact information associated with this number has changed. * <P> * Type: TEXT * </P> * * @hide */ public static final String CACHED_NORMALIZED_NUMBER = "normalized_number"; /** * The cached photo id of the picture associated with the phone number, if it exists. This value is not guaranteed to be * current, if the contact information associated with this number has changed. * <P> * Type: INTEGER (long) * </P> * * @hide */ public static final String CACHED_PHOTO_ID = "photo_id"; /** * The cached formatted phone number. This value is not guaranteed to be present. * <P> * Type: TEXT * </P> * * @hide */ public static final String CACHED_FORMATTED_NUMBER = "formatted_number"; /** * Adds a call to the call log. * * @param ci the CallerInfo object to get the target contact from. Can be null * if the contact is unknown. * @param context the context used to get the ContentResolver * @param number the phone number to be added to the calls db * @param presentation the number presenting rules set by the network for * "allowed", "payphone", "restricted" or "unknown" * @param callType enumerated values for "incoming", "outgoing", or "missed" * @param start time stamp for the call in milliseconds * @param duration call duration in seconds * * {@hide} * public static Uri addCall(CallerInfo ci, Context context, String number, int presentation, int callType, long start, int duration) { final ContentResolver resolver = context.getContentResolver(); // If this is a private number then set the number to Private, otherwise check // if the number field is empty and set the number to Unavailable if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) { number = CallerInfo.PRIVATE_NUMBER; if (ci != null) ci.name = ""; } else if (presentation == PhoneConstants.PRESENTATION_PAYPHONE) { number = CallerInfo.PAYPHONE_NUMBER; if (ci != null) ci.name = ""; } else if (TextUtils.isEmpty(number) || presentation == PhoneConstants.PRESENTATION_UNKNOWN) { number = CallerInfo.UNKNOWN_NUMBER; if (ci != null) ci.name = ""; } ContentValues values = new ContentValues(5); values.put(NUMBER, number); values.put(TYPE, Integer.valueOf(callType)); values.put(DATE, Long.valueOf(start)); values.put(DURATION, Long.valueOf(duration)); values.put(NEW, Integer.valueOf(1)); if (callType == MISSED_TYPE) { values.put(IS_READ, Integer.valueOf(0)); } if (ci != null) { values.put(CACHED_NAME, ci.name); values.put(CACHED_NUMBER_TYPE, ci.numberType); values.put(CACHED_NUMBER_LABEL, ci.numberLabel); } if ((ci != null) && (ci.person_id > 0)) { // Update usage information for the number associated with the contact ID. // We need to use both the number and the ID for obtaining a data ID since other // contacts may have the same number. final Cursor cursor; // We should prefer normalized one (probably coming from // Phone.NORMALIZED_NUMBER column) first. If it isn't available try others. if (ci.normalizedNumber != null) { final String normalizedPhoneNumber = ci.normalizedNumber; cursor = resolver.query(Phone.CONTENT_URI, new String[] { Phone._ID }, Phone.CONTACT_ID + " =? AND " + Phone.NORMALIZED_NUMBER + " =?", new String[] { String.valueOf(ci.person_id), normalizedPhoneNumber}, null); } else { final String phoneNumber = ci.phoneNumber != null ? ci.phoneNumber : number; cursor = resolver.query( Uri.withAppendedPath(Callable.CONTENT_FILTER_URI, Uri.encode(phoneNumber)), new String[] { Phone._ID }, Phone.CONTACT_ID + " =?", new String[] { String.valueOf(ci.person_id) }, null); } if (cursor != null) { try { if (cursor.getCount() > 0 && cursor.moveToFirst()) { final Uri feedbackUri = DataUsageFeedback.FEEDBACK_URI.buildUpon() .appendPath(cursor.getString(0)) .appendQueryParameter(DataUsageFeedback.USAGE_TYPE, DataUsageFeedback.USAGE_TYPE_CALL) .build(); resolver.update(feedbackUri, new ContentValues(), null, null); } } finally { cursor.close(); } } } Uri result = resolver.insert(CONTENT_URI, values); removeExpiredEntries(context); return result; } */ /** * Query the call log database for the last dialed number. * @param context Used to get the content resolver. * @return The last phone number dialed (outgoing) or an empty * string if none exist yet. */ public static String getLastOutgoingCall(Context context) { final ContentResolver resolver = context.getContentResolver(); Cursor c = null; try { c = resolver.query(CONTENT_URI, new String[] { NUMBER }, TYPE + " = " + OUTGOING_TYPE, null, DEFAULT_SORT_ORDER + " LIMIT 1"); if (c == null || !c.moveToFirst()) { return ""; } return c.getString(0); } finally { if (c != null) c.close(); } } /* private static void removeExpiredEntries(Context context) { final ContentResolver resolver = context.getContentResolver(); resolver.delete(CONTENT_URI, "_id IN " + "(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER + " LIMIT -1 OFFSET 500)", null); } */ } }