一,在AndroidManifest.xml中添加权限
二,在Activity中显示出来
注:高版本需要动态申请权限
public class MainActivity extends Activity {
private TextView show;
String[] allpermissions=new String[]{Manifest.permission.READ_CALL_LOG,Manifest.permission.READ_CONTACTS};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
show = (TextView)findViewById(R.id.calllog);
applypermission();
@SuppressLint("MissingPermission") Cursor cursor = getContentResolver().query(Calls.CONTENT_URI,
new String[] { Calls.DURATION, Calls.TYPE, Calls.DATE },
null,
null,
Calls.DEFAULT_SORT_ORDER);
MainActivity.this.startManagingCursor(cursor);
boolean hasRecord = cursor.moveToFirst();
long incoming = 0L;
long outgoing = 0L;
int count = 0;
while (hasRecord) {
int type = cursor.getInt(cursor.getColumnIndex(Calls.TYPE));
long duration = cursor.getLong(cursor.getColumnIndex(Calls.DURATION));
switch (type) {
case Calls.INCOMING_TYPE:
incoming += duration;
break;
case Calls.OUTGOING_TYPE:
outgoing += duration;
default:
break;
}
count++;
hasRecord = cursor.moveToNext();
}
Toast.makeText(MainActivity.this,
"共计 " + count + "次通话 . 总通话时长 " + (incoming + outgoing) + "秒. 其中接听 " + incoming + " 秒, 拔打 "
+ outgoing + " 秒.",
Toast.LENGTH_LONG).show();
show.setText("共计 " + count + "次通话 . 总通话时长 " + (incoming + outgoing) + "秒. 其中接听 " + incoming + " 秒, 拔打 "
+ outgoing + " 秒.");
}
public void applypermission(){
if(Build.VERSION.SDK_INT>=23){
boolean needapply=false;
for(int i=0;i三,源码部分
通话记录存在数据库contacts2.db的calls表里面:
frameworks\base\core\java\android\provider\CallLog.java
/**
* 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");
/**
* Query parameter used to limit the number of call logs returned.
*
* TYPE: integer
*/
public static final String LIMIT_PARAM_KEY = "limit";
/**
* Query parameter used to specify the starting record to return.
*
* 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.
*
* TYPE: Boolean
*
* 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";
/**
* Content uri with {@link #ALLOW_VOICEMAILS_PARAM_KEY} set. This can directly be used to
* access call log entries that includes voicemail records.
*
* @hide
*/
public static final Uri CONTENT_URI_WITH_VOICEMAIL = CONTENT_URI.buildUpon()
.appendQueryParameter(ALLOW_VOICEMAILS_PARAM_KEY, "true")
.build();
/**
* 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";
Type: INTEGER (int) /**
* The type of the call (incoming, outgoing or missed).
*
*/
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;
/**
* Call log type for voicemails.
* @hide
*/
public static final int VOICEMAIL_TYPE = 4;
Type: TEXT /**
* The phone number as the user entered it.
*
*/
public static final String NUMBER = "number";
/**
* The number presenting rules set by the network.
*
*
* Allowed values:
*
*
*
*
*
*
*
Type: INTEGER
/** Number is allowed to display for caller id. */
public static final int PRESENTATION_ALLOWED = 1;
/** Number is blocked by user. */
public static final int PRESENTATION_RESTRICTED = 2;
/** Number is not specified or unknown by network. */
public static final int PRESENTATION_UNKNOWN = 3;
/** Number is a pay phone. */
public static final int PRESENTATION_PAYPHONE = 4;
/**
* The ISO 3166-1 two letters country code of the country where the
* user received or made the call.
*
* Type: TEXT
*
*
* @hide
*/
public static final String COUNTRY_ISO = "countryiso";
Type: INTEGER (long) /**
* The date the call occured, in milliseconds since the epoch
*
*/
public static final String DATE = "date";
/**
* duration type for active.
* @hide
*/
public static final int DURATION_TYPE_ACTIVE = 0;
/**
* duration type for call out time.
* @hide
*/
public static final int DURATION_TYPE_CALLOUT = 1;
Type: INTEGER (long) /**
* The duration of the call in seconds
*
*/
public static final String DURATION = "duration";
Type: INTEGER (long) /**
* The type of the duration
*
* @hide
*/
public static final String DURATION_TYPE = "duration_type";
Type: INTEGER (long) /**
* The call log details for mixed calllog type
*
* @hide
*/
public static final String VIDEO_CALL_DURATION = "video_call_duration";
Type: INTEGER (boolean) /**
* Whether or not the call has been acknowledged
*
*/
public static final String NEW = "new";
Type: TEXT /**
* 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.
*
*/
public static final String CACHED_NAME = "name";
Type: INTEGER /**
* 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.
*
*/
public static final String CACHED_NUMBER_TYPE = "numbertype";
Type: TEXT /**
* 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.
*
*/
public static final String CACHED_NUMBER_LABEL = "numberlabel";
Type: TEXT /**
* URI of the voicemail entry. Populated only for {@link #VOICEMAIL_TYPE}.
*
* @hide
*/
public static final String VOICEMAIL_URI = "voicemail_uri";
Type: INTEGER (boolean) /**
* Whether this item has been read or otherwise consumed by the user.
*
* 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.
*
*/
public static final String IS_READ = "is_read";
Type: TEXT /**
* A geocoded location for the number associated with this call.
*
* The string represents a city, state, or country associated with the number.
*
* @hide
*/
public static final String GEOCODED_LOCATION = "geocoded_location";
Type: TEXT /**
* 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.
*
* @hide
*/
public static final String CACHED_LOOKUP_URI = "lookup_uri";
Type: TEXT /**
* 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.
*
* @hide
*/
public static final String CACHED_MATCHED_NUMBER = "matched_number";
Type: TEXT /**
* 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.
*
* @hide
*/
public static final String CACHED_NORMALIZED_NUMBER = "normalized_number";
Type: INTEGER (long) /**
* 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.
*
* @hide
*/
public static final String CACHED_PHOTO_ID = "photo_id";
Type: TEXT /**
* The cached formatted phone number.
* This value is not guaranteed to be present.
*
* @hide
*/
public static final String CACHED_FORMATTED_NUMBER = "formatted_number";
Type: Integer /**
* The subscription id.
*
* @hide
*/
public static final String SUBSCRIPTION = MSimConstants.SUBSCRIPTION_KEY;
/**
* 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 enum value from PhoneConstants.PRESENTATION_xxx, which
* is set by the network and denotes the number presenting rules 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) {
return addCall(ci, context, number, presentation, callType, start, duration,
MSimConstants.DEFAULT_SUBSCRIPTION, DURATION_TYPE_ACTIVE);
}
/**
* Add a call to the call log for multi sim, and it can be used in TSTS.
*
* @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
* @param subscription valid value is 0,1 or 2
*
* {@hide}
*/
public static Uri addCall(CallerInfo ci, Context context, String number,
int presentation, int callType, long start, int duration, int subscription) {
return addCall(ci, context, number, presentation, callType, start, duration,
subscription, DURATION_TYPE_ACTIVE);
}
/**
* Add a call to the call log for multi sim, and it can be used in TSTS.
*
* @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
* @param subscription valid value is 0,1 or 2
* @param durationType valid value is 0 or 1
*
* {@hide}
*/
public static Uri addCall(CallerInfo ci, Context context, String number,
int presentation, int callType, long start, int duration, int subscription,
int durationType) {
return addCall(ci, context, number, presentation, callType, start, duration,
subscription, durationType, null);
}
/**
* Add a call to the call log for with call type details for LTE
*
* @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
* @param subscription valid value is 0,1 or 2
* @param durationType valid value is 0 or 1
*
* {@hide}
*/
public static Uri addCall(CallerInfo ci, Context context, String number,
int presentation, int callType, long start, int duration, int subscription,
int durationType, String videocallduration) {
final ContentResolver resolver = context.getContentResolver();
int numberPresentation = PRESENTATION_ALLOWED;
// Remap network specified number presentation types
// PhoneConstants.PRESENTATION_xxx to calllog number presentation types
// Calls.PRESENTATION_xxx, in order to insulate the persistent calllog
// from any future radio changes.
// If the number field is empty set the presentation type to Unknown.
if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) {
numberPresentation = PRESENTATION_RESTRICTED;
} else if (presentation == PhoneConstants.PRESENTATION_PAYPHONE) {
numberPresentation = PRESENTATION_PAYPHONE;
} else if (TextUtils.isEmpty(number)
|| presentation == PhoneConstants.PRESENTATION_UNKNOWN) {
numberPresentation = PRESENTATION_UNKNOWN;
}
if (numberPresentation != PRESENTATION_ALLOWED) {
number = "";
if (ci != null) {
ci.name = "";
}
}
ContentValues values = new ContentValues(6);
values.put(NUMBER, number);
values.put(NUMBER_PRESENTATION, Integer.valueOf(numberPresentation));
values.put(TYPE, Integer.valueOf(callType));
values.put(DATE, Long.valueOf(start));
values.put(DURATION, Long.valueOf(duration));
values.put(NEW, Integer.valueOf(1));
values.put(SUBSCRIPTION, Integer.valueOf(subscription));
values.put(DURATION_TYPE, Integer.valueOf(durationType));
if (videocallduration != null){
values.put(VIDEO_CALL_DURATION, videocallduration);
}
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);
}
}
}
packages\providers\ContactsProvider\src\com\android\providers\contacts\ContactsDatabaseHelper.java中创建数据库
db.execSQL("CREATE TABLE " + Tables.CALLS + " (" +
Calls._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
Calls.NUMBER + " TEXT," +
Calls.NUMBER_PRESENTATION + " INTEGER NOT NULL DEFAULT " +
Calls.PRESENTATION_ALLOWED + "," +
Calls.DATE + " INTEGER," +
Calls.DURATION + " INTEGER," +
Calls.TYPE + " INTEGER," +
Calls.NEW + " INTEGER," +
Calls.CACHED_NAME + " TEXT," +
Calls.CACHED_NUMBER_TYPE + " INTEGER," +
Calls.CACHED_NUMBER_LABEL + " TEXT," +
Calls.COUNTRY_ISO + " TEXT," +
Calls.VOICEMAIL_URI + " TEXT," +
Calls.IS_READ + " INTEGER," +
Calls.GEOCODED_LOCATION + " TEXT," +
Calls.CACHED_LOOKUP_URI + " TEXT," +
Calls.CACHED_MATCHED_NUMBER + " TEXT," +
Calls.CACHED_NORMALIZED_NUMBER + " TEXT," +
Calls.CACHED_PHOTO_ID + " INTEGER NOT NULL DEFAULT 0," +
Calls.CACHED_FORMATTED_NUMBER + " TEXT," +
Voicemails._DATA + " TEXT," +
Voicemails.HAS_CONTENT + " INTEGER," +
Voicemails.MIME_TYPE + " TEXT," +
Voicemails.SOURCE_DATA + " TEXT," +
Voicemails.SOURCE_PACKAGE + " TEXT," +
Voicemails.STATE + " INTEGER," +
Calls.SUBSCRIPTION + " INTEGER NOT NULL DEFAULT 0," +
Calls.DURATION_TYPE + " INTEGER NOT NULL DEFAULT " + Calls.DURATION_TYPE_ACTIVE + "," +
Calls.VIDEO_CALL_DURATION + " TEXT" +
");");
packages\providers\ContactsProvider\src\com\android\providers\contacts\CallLogProvider.java使用ContentProvider提供方法接口给外部使用。
public class CallLogProvider extends ContentProvider {
/** Selection clause to use to exclude voicemail records. */
private static final String EXCLUDE_VOICEMAIL_SELECTION = getInequalityClause(
Calls.TYPE, 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
static {
// Calls projection map
sCallsProjectionMap = new HashMap
sCallsProjectionMap.put(Calls._ID, Calls._ID);
sCallsProjectionMap.put(Calls.NUMBER, Calls.NUMBER);
sCallsProjectionMap.put(Calls.NUMBER_PRESENTATION, Calls.NUMBER_PRESENTATION);
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);
// To match the multisim, add the subscription for the call log to mark the call log state.
sCallsProjectionMap.put(Calls.SUBSCRIPTION, Calls.SUBSCRIPTION);
sCallsProjectionMap.put(Calls.DURATION_TYPE, Calls.DURATION_TYPE);
// add the video call duration for the call log details.
sCallsProjectionMap.put(Calls.VIDEO_CALL_DURATION, Calls.VIDEO_CALL_DURATION);
}
private ContactsDatabaseHelper mDbHelper;
private DatabaseUtils.InsertHelper mCallsInserter;
private boolean mUseStrictPhoneNumberComparation;
private VoicemailPermissions mVoicemailPermissions;
private CallLogInsertionHelper mCallLogInsertionHelper;
@Override
public boolean onCreate() {
setAppOps(AppOpsManager.OP_READ_CALL_LOG, AppOpsManager.OP_WRITE_CALL_LOG);
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) {
final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
qb.setTables(Tables.CALLS);
qb.setProjectionMap(sCallsProjectionMap);
qb.setStrict(true);
final SelectionBuilder selectionBuilder = new SelectionBuilder(selection);
checkVoicemailPermissionAndAddRestriction(uri, selectionBuilder);
final int match = sURIMatcher.match(uri);
switch (match) {
case CALLS:
break;
case CALLS_ID: {
selectionBuilder.addClause(getEqualityClause(Calls._ID,
parseCallIdFromUri(uri)));
break;
}
case CALLS_FILTER: {
List
String phoneNumber = pathSegments.size() >= 2 ? pathSegments.get(2) : null;
if (!TextUtils.isEmpty(phoneNumber)) {
qb.appendWhere("PHONE_NUMBERS_EQUAL(number, ");
qb.appendWhereEscapeString(phoneNumber);
qb.appendWhere(mUseStrictPhoneNumberComparation ? ", 1)" : ", 0)");
} else {
qb.appendWhere(Calls.NUMBER_PRESENTATION + "!="
+ Calls.PRESENTATION_ALLOWED);
}
break;
}
default:
throw new IllegalArgumentException("Unknown URL " + uri);
}
final int limit = getIntParam(uri, Calls.LIMIT_PARAM_KEY, 0);
final int offset = getIntParam(uri, Calls.OFFSET_PARAM_KEY, 0);
String limitClause = null;
if (limit > 0) {
limitClause = offset + "," + limit;
}
final SQLiteDatabase db = mDbHelper.getReadableDatabase();
final Cursor c = qb.query(db, projection, selectionBuilder.build(), selectionArgs, null,
null, sortOrder, limitClause);
if (c != null) {
c.setNotificationUri(getContext().getContentResolver(), CallLog.CONTENT_URI);
}
return c;
}
/**
* Gets an integer query parameter from a given uri.
*
* @param uri The uri to extract the query parameter from.
* @param key The query parameter key.
* @param defaultValue A default value to return if the query parameter does not exist.
* @return The value from the query parameter in the Uri. Or the default value if the parameter
* does not exist in the uri.
* @throws IllegalArgumentException when the value in the query parameter is not an integer.
*/
private int getIntParam(Uri uri, String key, int defaultValue) {
String valueString = uri.getQueryParameter(key);
if (valueString == null) {
return defaultValue;
}
try {
return Integer.parseInt(valueString);
} catch (NumberFormatException e) {
String msg = "Integer required for " + key + " parameter but value '" + valueString +
"' was found instead.";
throw new IllegalArgumentException(msg, e);
}
}
@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);
case CALLS_ID:
return getDatabaseModifier(db).delete(Tables.CALLS,
new SelectionBuilder(Calls._ID + "=?").build(), new String[] {
uri.getLastPathSegment()
});
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 static final Integer VOICEMAIL_TYPE = new Integer(Calls.VOICEMAIL_TYPE);
private boolean hasVoicemailValue(ContentValues values) {
return VOICEMAIL_TYPE.equals(values.getAsInteger(Calls.TYPE));
}
/**
* Checks if the supplied uri requests to include voicemails and take appropriate
* action.
*
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 long parseCallIdFromUri(Uri uri) {
try {
return Long.parseLong(uri.getPathSegments().get(1));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Invalid call id in uri: " + uri, e);
}
}
}
android:syncable="false" android:multiprocess="false"
android:exported="true"
android:readPermission="android.permission.READ_CALL_LOG"
android:writePermission="android.permission.WRITE_CALL_LOG">
其他进程,添加读写权限即可。