/*
* 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.
*/
package android.provider;
import com.android.internal.telephony.CallerInfo;
import com.android.internal.telephony.Connection;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.text.TextUtils;
/**
* The CallLog provider contains information about placed and received calls.
*/
public class CallLog {
public static final String AUTHORITY = "call_log";
/**
* The content:// style URL for this provider
*/
public static final Uri CONTENT_URI =
Uri.parse("content://" + AUTHORITY);
/**
* Contains the recent calls.
*/
public static class Calls implements BaseColumns {
/**
* The content:// style URL for this table
*/
public static final Uri CONTENT_URI =
Uri.parse("content://call_log/calls");
/**
* The content:// style URL for filtering this table on phone numbers
*/
public static final Uri CONTENT_FILTER_URI =
Uri.parse("content://call_log/calls/filter");
/**
* 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/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/calls";
/**
* The type of the call (incoming, outgoing or missed).
* <P>Type: INTEGER (int)</P>
*/
public static final String TYPE = "type";
public static final int INCOMING_TYPE = 1;
public static final int OUTGOING_TYPE = 2;
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 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";
/**
* 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 == Connection.PRESENTATION_RESTRICTED) {
number = CallerInfo.PRIVATE_NUMBER;
if (ci != null) ci.name = "";
} else if (presentation == Connection.PRESENTATION_PAYPHONE) {
number = CallerInfo.PAYPHONE_NUMBER;
if (ci != null) ci.name = "";
} else if (TextUtils.isEmpty(number)
|| presentation == Connection.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 (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)) {
ContactsContract.Contacts.markAsContacted(resolver, ci.person_id);
}
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);
}
}
}
/*
* Copyright (C) 2009 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
*/
package com.android.providers.contacts;
import static com.android.providers.contacts.util.DbQueryUtils.checkForSupportedColumns;
import static com.android.providers.contacts.util.DbQueryUtils.getEqualityClause;
import static com.android.providers.contacts.util.DbQueryUtils.getInequalityClause;
import com.android.providers.contacts.ContactsDatabaseHelper.Tables;
import com.android.providers.contacts.util.SelectionBuilder;
import com.google.common.annotations.VisibleForTesting;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.DatabaseUtils;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.provider.CallLog;
import android.provider.CallLog.Calls;
import android.util.Log;
import java.util.HashMap;
/**
* Call log content provider.
*/
public class CallLogProvider extends ContentProvider {
/** Selection clause to use to exclude voicemail records. */
private static final String EXCLUDE_VOICEMAIL_SELECTION = getInequalityClause(
Calls.TYPE, Integer.toString(Calls.VOICEMAIL_TYPE));
private static final int CALLS = 1;
private static final int CALLS_ID = 2;
private static final int CALLS_FILTER = 3;
private static final UriMatcher sURIMatcher = new UriMatcher(UriMatcher.NO_MATCH);
static {
sURIMatcher.addURI(CallLog.AUTHORITY, "calls", CALLS);
sURIMatcher.addURI(CallLog.AUTHORITY, "calls/#", CALLS_ID);
sURIMatcher.addURI(CallLog.AUTHORITY, "calls/filter/*", CALLS_FILTER);
}
private static final HashMap<String, String> sCallsProjectionMap;
static {
// Calls projection map
sCallsProjectionMap = new HashMap<String, String>();
sCallsProjectionMap.put(Calls._ID, Calls._ID);
sCallsProjectionMap.put(Calls.NUMBER, Calls.NUMBER);
sCallsProjectionMap.put(Calls.DATE, Calls.DATE);
sCallsProjectionMap.put(Calls.DURATION, Calls.DURATION);
sCallsProjectionMap.put(Calls.TYPE, Calls.TYPE);
sCallsProjectionMap.put(Calls.NEW, Calls.NEW);
sCallsProjectionMap.put(Calls.VOICEMAIL_URI, Calls.VOICEMAIL_URI);
sCallsProjectionMap.put(Calls.IS_READ, Calls.IS_READ);
sCallsProjectionMap.put(Calls.CACHED_NAME, Calls.CACHED_NAME);
sCallsProjectionMap.put(Calls.CACHED_NUMBER_TYPE, Calls.CACHED_NUMBER_TYPE);
sCallsProjectionMap.put(Calls.CACHED_NUMBER_LABEL, Calls.CACHED_NUMBER_LABEL);
sCallsProjectionMap.put(Calls.COUNTRY_ISO, Calls.COUNTRY_ISO);
sCallsProjectionMap.put(Calls.GEOCODED_LOCATION, Calls.GEOCODED_LOCATION);
sCallsProjectionMap.put(Calls.CACHED_LOOKUP_URI, Calls.CACHED_LOOKUP_URI);
sCallsProjectionMap.put(Calls.CACHED_MATCHED_NUMBER, Calls.CACHED_MATCHED_NUMBER);
sCallsProjectionMap.put(Calls.CACHED_NORMALIZED_NUMBER, Calls.CACHED_NORMALIZED_NUMBER);
sCallsProjectionMap.put(Calls.CACHED_PHOTO_ID, Calls.CACHED_PHOTO_ID);
sCallsProjectionMap.put(Calls.CACHED_FORMATTED_NUMBER, Calls.CACHED_FORMATTED_NUMBER);
}
private ContactsDatabaseHelper mDbHelper;
private DatabaseUtils.InsertHelper mCallsInserter;
private boolean mUseStrictPhoneNumberComparation;
private VoicemailPermissions mVoicemailPermissions;
private CallLogInsertionHelper mCallLogInsertionHelper;
@Override
public boolean onCreate() {
if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
Log.d(Constants.PERFORMANCE_TAG, "CallLogProvider.onCreate start");
}
final Context context = getContext();
mDbHelper = getDatabaseHelper(context);
mUseStrictPhoneNumberComparation =
context.getResources().getBoolean(
com.android.internal.R.bool.config_use_strict_phone_number_comparation);
mVoicemailPermissions = new VoicemailPermissions(context);
mCallLogInsertionHelper = createCallLogInsertionHelper(context);
if (Log.isLoggable(Constants.PERFORMANCE_TAG, Log.DEBUG)) {
Log.d(Constants.PERFORMANCE_TAG, "CallLogProvider.onCreate finish");
}
return true;
}
@VisibleForTesting
protected CallLogInsertionHelper createCallLogInsertionHelper(final Context context) {
return DefaultCallLogInsertionHelper.getInstance(context);
}
@VisibleForTesting
protected ContactsDatabaseHelper getDatabaseHelper(final Context context) {
return ContactsDatabaseHelper.getInstance(context);
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(Tables.CALLS);
qb.setProjectionMap(sCallsProjectionMap);
qb.setStrict(true);
SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
checkVoicemailPermissionAndAddRestriction(uri, selectionBuilder);
int match = sURIMatcher.match(uri);
switch (match) {
case CALLS:
break;
case CALLS_ID: {
selectionBuilder.addClause(getEqualityClause(Calls._ID,
parseCallIdFromUri(uri)));
break;
}
case CALLS_FILTER: {
String phoneNumber = uri.getPathSegments().get(2);
qb.appendWhere("PHONE_NUMBERS_EQUAL(number, ");
qb.appendWhereEscapeString(phoneNumber);
qb.appendWhere(mUseStrictPhoneNumberComparation ? ", 1)" : ", 0)");
break;
}
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
final SQLiteDatabase db = mDbHelper.getReadableDatabase();
Cursor c = qb.query(db, projection, selectionBuilder.build(), selectionArgs, null, null,
sortOrder, null);
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), CallLog.CONTENT_URI);
}
return c;
}
@Override
public String getType(Uri uri) {
int match = sURIMatcher.match(uri);
switch (match) {
case CALLS:
return Calls.CONTENT_TYPE;
case CALLS_ID:
return Calls.CONTENT_ITEM_TYPE;
case CALLS_FILTER:
return Calls.CONTENT_TYPE;
default:
throw new IllegalArgumentException("Unknown URI: " + uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
checkForSupportedColumns(sCallsProjectionMap, values);
// Inserting a voicemail record through call_log requires the voicemail
// permission and also requires the additional voicemail param set.
if (hasVoicemailValue(values)) {
checkIsAllowVoicemailRequest(uri);
mVoicemailPermissions.checkCallerHasFullAccess();
}
if (mCallsInserter == null) {
SQLiteDatabase db = mDbHelper.getWritableDatabase();
mCallsInserter = new DatabaseUtils.InsertHelper(db, Tables.CALLS);
}
ContentValues copiedValues = new ContentValues(values);
// Add the computed fields to the copied values.
mCallLogInsertionHelper.addComputedValues(copiedValues);
long rowId = getDatabaseModifier(mCallsInserter).insert(copiedValues);
if (rowId > 0) {
return ContentUris.withAppendedId(uri, rowId);
}
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
checkForSupportedColumns(sCallsProjectionMap, values);
// Request that involves changing record type to voicemail requires the
// voicemail param set in the uri.
if (hasVoicemailValue(values)) {
checkIsAllowVoicemailRequest(uri);
}
SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
checkVoicemailPermissionAndAddRestriction(uri, selectionBuilder);
final SQLiteDatabase db = mDbHelper.getWritableDatabase();
final int matchedUriId = sURIMatcher.match(uri);
switch (matchedUriId) {
case CALLS:
break;
case CALLS_ID:
selectionBuilder.addClause(getEqualityClause(Calls._ID, parseCallIdFromUri(uri)));
break;
default:
throw new UnsupportedOperationException("Cannot update URL: " + uri);
}
return getDatabaseModifier(db).update(Tables.CALLS, values, selectionBuilder.build(),
selectionArgs);
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
checkVoicemailPermissionAndAddRestriction(uri, selectionBuilder);
final SQLiteDatabase db = mDbHelper.getWritableDatabase();
final int matchedUriId = sURIMatcher.match(uri);
switch (matchedUriId) {
case CALLS:
return getDatabaseModifier(db).delete(Tables.CALLS,
selectionBuilder.build(), selectionArgs);
default:
throw new UnsupportedOperationException("Cannot delete that URL: " + uri);
}
}
// Work around to let the test code override the context. getContext() is final so cannot be
// overridden.
protected Context context() {
return getContext();
}
/**
* Returns a {@link DatabaseModifier} that takes care of sending necessary notifications
* after the operation is performed.
*/
private DatabaseModifier getDatabaseModifier(SQLiteDatabase db) {
return new DbModifierWithNotification(Tables.CALLS, db, context());
}
/**
* Same as {@link #getDatabaseModifier(SQLiteDatabase)} but used for insert helper operations
* only.
*/
private DatabaseModifier getDatabaseModifier(DatabaseUtils.InsertHelper insertHelper) {
return new DbModifierWithNotification(Tables.CALLS, insertHelper, context());
}
private boolean hasVoicemailValue(ContentValues values) {
return values.containsKey(Calls.TYPE) &&
values.getAsInteger(Calls.TYPE).equals(Calls.VOICEMAIL_TYPE);
}
/**
* Checks if the supplied uri requests to include voicemails and take appropriate
* action.
* <p> If voicemail is requested, then check for voicemail permissions. Otherwise
* modify the selection to restrict to non-voicemail entries only.
*/
private void checkVoicemailPermissionAndAddRestriction(Uri uri,
SelectionBuilder selectionBuilder) {
if (isAllowVoicemailRequest(uri)) {
mVoicemailPermissions.checkCallerHasFullAccess();
} else {
selectionBuilder.addClause(EXCLUDE_VOICEMAIL_SELECTION);
}
}
/**
* Determines if the supplied uri has the request to allow voicemails to be
* included.
*/
private boolean isAllowVoicemailRequest(Uri uri) {
return uri.getBooleanQueryParameter(Calls.ALLOW_VOICEMAILS_PARAM_KEY, false);
}
/**
* Checks to ensure that the given uri has allow_voicemail set. Used by
* insert and update operations to check that ContentValues with voicemail
* call type must use the voicemail uri.
* @throws IllegalArgumentException if allow_voicemail is not set.
*/
private void checkIsAllowVoicemailRequest(Uri uri) {
if (!isAllowVoicemailRequest(uri)) {
throw new IllegalArgumentException(
String.format("Uri %s cannot be used for voicemail record." +
" Please set '%s=true' in the uri.", uri,
Calls.ALLOW_VOICEMAILS_PARAM_KEY));
}
}
/**
* Parses the call Id from the given uri, assuming that this is a uri that
* matches CALLS_ID. For other uri types the behaviour is undefined.
* @throws IllegalArgumentException if the id included in the Uri is not a valid long value.
*/
private String parseCallIdFromUri(Uri uri) {
try {
Long id = Long.valueOf(uri.getPathSegments().get(1));
return id.toString();
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid call id in uri: " + uri, e);
}
}
}
android中获取联系人
ContentResolver cr = getContentResolver();
Cursor cursor = cr.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null);
while(cursor.moveToNext()){
//get name
int nameFiledColumnIndex = cursor.getColumnIndex(PhoneLookup.DISPLAY_NAME);
String contact = cursor.getString(nameFiledColumnIndex);
String[] PHONES_PROJECTION = new String[] {"_id","display_name","data1","data3"};//
String contactId = cursor.getString(cursor.getColumnIndex(PhoneLookup._ID));
Cursor phone = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, PHONES_PROJECTION,
ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=" + contactId, null, null);
//name type ..
while(phone.moveToNext()) {
int i = phone.getInt(0);
String str = phone.getString(1);
str = phone.getString(2);
str = phone.getString(3);
}
phone.close();
//addr
Cursor addrCur = cr.query(ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_URI ,
new String[]{"_id","data1","data2","data3"}, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=" + contactId , null, null);
while(addrCur.moveToNext()) {
int i = addrCur.getInt(0);
String str = addrCur.getString(1);
str = addrCur.getString(2);
str = addrCur.getString(3);
}
addrCur.close();
//email
Cursor emailCur = cr.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI ,
new String[]{"_id","data1","data2","data3"}, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + "=" + contactId , null, null);
while(emailCur.moveToNext()) {
int i = emailCur.getInt(0);
String str = emailCur.getString(1);
str = emailCur.getString(2);
str = emailCur.getString(3);
}
emailCur.close();
}
cursor.close();
android中获取通话记录
String str = "";
int type;
long callTime;
Date date;
String time= "";
ContentResolver cr = getContentResolver();
final Cursor cursor = cr.query(CallLog.Calls.CONTENT_URI, new String[]{CallLog.Calls.NUMBER,CallLog.Calls.CACHED_NAME,CallLog.Calls.TYPE, CallLog.Calls.DATE}, null, null,CallLog.Calls.DEFAULT_SORT_ORDER);
for (int i = 0; i < cursor.getCount(); i++) {
cursor.moveToPosition(i);
str = cursor.getString(0);
str = cursor.getString(1);
type = cursor.getInt(2);
SimpleDateFormat sfd = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
date = new Date(Long.parseLong(cursor.getString(3)));
time = sfd.format(date);
}
比如要用实现这么一个sql语句:
SELECT _id, number, date, duration, type, name, numbertype,
numberlabel, COUNT(*) FROM callsGROUP BY number,type,date/8640000
ORDER BY date DESC
这个在一般的SQL编译工具里都能正常运行,在ContentRosolver中有些不一样。
用ContentRosolver中的query这么写:
private static String CALLS_COUNT = "calls_count";
static final String[] CALL_LOG_PROJECTION = new String[] {
Calls._ID,
Calls.NUMBER,
Calls.DATE,
Calls.DURATION,
Calls.TYPE,
Calls.CACHED_NAME,
Calls.CACHED_NUMBER_TYPE,
Calls.CACHED_NUMBER_LABEL,
"COUNT(*) AS " + CALLS_COUNT
};
String selection = "0==0) GROUP BY ("+
Calls.NUMBER+"),("+
Calls.TYPE+"),("+
Calls.DATE+"/86400000";//这里没有左边括号"("和右边括号")" 程序会自己添加.
//0==0 一个=号和2个等于都可以.
rosolver.query(QUERY_TOKEN, null, Calls.CONTENT_URI,
CALL_LOG_PROJECTION, selection, null, Calls.DEFAULT_SORT_ORDER);
注意事项:
1 关键字“COUNT, AS, GROUP BY”的大小写
2 COUNT(*) 后需要跟AS ***
3 Android会将query中的参数整合成一条sql语句,其中会将selection的字符串自动加一个括号,形成 “WHERE
(*******)”的形式,所以要特别注意selection中有括号的情况
4 GROUP BY后面的字段应该加括号,用逗号隔开。
转化为sql语句正确的形式应该如下:
SELECT _id, number, date, duration, type, name, numbertype,
numberlabel, COUNT(*) FROM calls WHERE (0==0) GROUP BY
(number),(type),(date/8640000) ORDER BY date DESC