数据库
实现在ContactsProvider中,具体是packages/providers/ContactsProvider/src/com/android/providers/contacts/CallLogProvider.java
从6.0的代码开始有个packages/providers/CallLogProvider目录,我开始还以为CallLog单独分离出个apk,不过这个并不是,名字太误导人了。
创建
packages/providers/ContactsProvider/src/com/android/providers/contacts/ContactsDatabaseHelper.java
@Override
public void onCreate(SQLiteDatabase db) {
...
db.execSQL("CREATE TABLE " + Tables.CALLS + " (" +
Calls._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
Calls.NUMBER + " TEXT," + //号码
Calls.NUMBER_PRESENTATION + " INTEGER NOT NULL DEFAULT " + //cnap相关,是否显示号码
Calls.PRESENTATION_ALLOWED + "," +
Calls.DATE + " INTEGER," + //创建时间点
Calls.DURATION + " INTEGER," + //通话时长
Calls.DATA_USAGE + " INTEGER," + //通话消耗的数据流量,指sip通话等
Calls.TYPE + " INTEGER," + //通话类型,来电、拨号等等
Calls.FEATURES + " INTEGER NOT NULL DEFAULT 0," + //视频通话时为1
Calls.PHONE_ACCOUNT_COMPONENT_NAME + " TEXT," + //注册到Telecom的账号名字,一般就是com.android.phone/com.android.services.telephony.TelephonyConnectionService
Calls.PHONE_ACCOUNT_ID + " TEXT," + //sim卡的通话记录可视为等同于subid
Calls.PHONE_ACCOUNT_ADDRESS + " TEXT," + //可视为sim卡自己的号码
Calls.PHONE_ACCOUNT_HIDDEN + " INTEGER NOT NULL DEFAULT 0," + //不插SIM卡时指示该SIM卡记录不显示
Calls.SUB_ID + " INTEGER DEFAULT -1," + //
Calls.NEW + " INTEGER," + //这个值在插入的时候是1,通话记录进入过后设置为0
Calls.CACHED_NAME + " TEXT," + //号码相关联系人名字
Calls.CACHED_NUMBER_TYPE + " INTEGER," + //号码类型int值
Calls.CACHED_NUMBER_LABEL + " TEXT," + //号码类型,手机、座机等
Calls.COUNTRY_ISO + " TEXT," + //国家iso代码
Calls.VOICEMAIL_URI + " TEXT," + //语音邮箱uri
Calls.IS_READ + " INTEGER," + //未接来电记录用的,看过后设置为0
Calls.GEOCODED_LOCATION + " TEXT," + //归属地
Calls.CACHED_LOOKUP_URI + " TEXT," + //联系人lookup uri
Calls.CACHED_MATCHED_NUMBER + " TEXT," + //和该号码匹配的联系人号码
Calls.CACHED_NORMALIZED_NUMBER + " TEXT," + //同Calls.CACHED_MATCHED_NUMBER,不过格式是e164
Calls.CACHED_PHOTO_ID + " INTEGER NOT NULL DEFAULT 0," + //联系人头像id
Calls.CACHED_PHOTO_URI + " TEXT," + //联系人头像uri
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.TRANSCRIPTION + " TEXT," +
Voicemails.STATE + " INTEGER," +
Voicemails.DIRTY + " INTEGER NOT NULL DEFAULT 0," +
Voicemails.DELETED + " INTEGER NOT NULL DEFAULT 0," +
Calls.RAW_CONTACT_ID + " INTEGER DEFAULT NULL," + //add by MTK 对应联系人的RAW_CONTACT_ID
Calls.DATA_ID + " INTEGER DEFAULT NULL," + //add by MTK 对应联系人号码的DATA_ID
Calls.IP_PREFIX + " TEXT," + //add by MTK ip拨号前缀
Calls.CONFERENCE_CALL_ID + " INTEGER" + //add by MTK 记录会议通话id
");");
...
}
各个字段见注释,语音邮箱没有用过所以没写。注意联系人的相关字段都以CACHED_开头,这个表示这里的数据和联系人表中的不一定完全保持同步。
查询
由于calls表中的联系人数据不一定和联系人数据库中的完全同步,所以查询并不是直接查询calls表,而是和data表联合起来一起查询
private static final int CALLS_JION_DATA_VIEW = 5;
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
String sortOrder) {
...
switch (match) {
...
case CALLS_JION_DATA_VIEW:
case CONFERENCE_CALLS_ID: {
mCallLogProviderEx.queryCallLog(uri, projection, selection,
selectionArgs, sortOrder, match, qb, selectionBuilder, null); //设置qb
break;
...
final Cursor c = qb.query(db, projection, selectionBuilder.build(), selectionArgs, null,
null, sortOrder, limitClause); //最终查询的执行
...
return c;
}
调用mCallLogProviderEx处理SQLiteQueryBuilder
packages/providers/ContactsProvider/src/com/mediatek/providers/contacts/CallLogProviderEx.java
public SQLiteQueryBuilder queryCallLog(Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder, int match,
SQLiteQueryBuilder qb, SelectionBuilder selectionBuilder, Long parseCallId) {
...
case CALLS_JION_DATA_VIEW: {
qb.setTables(sStableCallsJoinData); //对应的表
qb.setProjectionMap(sCallsJoinDataViewProjectionMap); //对应的字段
qb.setStrict(true);
...
}
其中sStableCallsJoinData是个比较复杂的联合语句
private static final String sStableCallsJoinData = Tables.CALLS
+ " LEFT JOIN "
+ Tables.CONFERENCE_CALLS + " ON "
+ Calls.CONFERENCE_CALL_ID + "=" + Tables.CONFERENCE_CALLS + "." + ConferenceCalls._ID
+ " LEFT JOIN "
+ " (SELECT * FROM " + Views.DATA + " WHERE " + Data._ID + " IN " + "(SELECT "
+ Calls.DATA_ID + " FROM " + Tables.CALLS + ")) AS " + Views.DATA + " ON("
+ Tables.CALLS + "." + Calls.DATA_ID + " = " + Views.DATA + "." + Data._ID + ")";
这样最终查询就能生成正确的数据。
通话记录插入
packages/services/Telecomm/src/com/android/server/telecom/CallLogManager.java
@Override
public void onCallStateChanged(Call call, int oldState, int newState) {
...
logCall(call, type);
...
}
通话结束后的回调方法中开始记录数据
void logCall(Call call, int callLogType) {
...
logCall(call.getCallerInfo(), logNumber, call.getHandlePresentation(),
callLogType, callFeatures, accountHandle, creationTime, age, null,
call.getConferenceCallLogId() /* M: For Volte Conference call */);
}
private void logCall(
CallerInfo callerInfo,
String number,
int presentation,
int callType,
int features,
PhoneAccountHandle accountHandle,
long start,
long duration,
Long dataUsage,
long conferenceCallLogId /* M: For Volte Conference call */) {
...
AddCallArgs args = new AddCallArgs(mContext, callerInfo, number, presentation,
callType, features, accountHandle, start, duration, dataUsage,
conferenceCallLogId /* M: For Conference call */);
logCallAsync(args);
...
}
创建了一个AddCallArgs对象,然后调用logCallAsync
private static class AddCallArgs { //该类是CallLogManager内部类,就是通话记录的封装
...
public final Context context;
public final CallerInfo callerInfo;
public final String number;
public final int presentation;
public final int callType;
public final int features;
public final PhoneAccountHandle accountHandle;
public final long timestamp;
public final int durationInSec;
public final Long dataUsage;
/// M: For Volte conference call calllog
public final long conferenceCallLogId;
}
public AsyncTask<AddCallArgs, Void, Uri[]> logCallAsync(AddCallArgs args) {
return new LogCallAsyncTask().execute(args);
}
创建了一个新的AsyncTask执行任务。
private class LogCallAsyncTask extends AsyncTask<AddCallArgs, Void, Uri[]> { //内部类
private AddCallArgs[] mAddCallArgs = null;
@Override
protected Uri[] doInBackground(AddCallArgs... callList) {
mAddCallArgs = callList;
int count = callList.length;
Uri[] result = new Uri[count];
for (int i = 0; i < count; i++) {
AddCallArgs c = callList[i];
// May block.
result[i] = Calls.addCall(c.callerInfo, c.context, c.number, c.presentation,
c.callType, c.features, c.accountHandle, c.timestamp, c.durationInSec,
c.dataUsage, true /* addForAllUsers */,
c.conferenceCallLogId/* M: For Volte conference call calllog */); 使用framework中的代码加入到数据库中
}
return result;
}
...
}
在线程中完成了插入数据库的操作。Calls类定义在frameworks/base/core/java/android/provider/CallLog.java中
public static Uri addCall(CallerInfo ci, Context context, String number,
int presentation, int callType, int features, PhoneAccountHandle accountHandle,
long start, int duration, Long dataUsage, boolean addForAllUsers,
boolean is_read, long conferenceCallId) {
...
ContentValues values = new ContentValues(6);
values.put(NUMBER, number);
values.put(NUMBER_PRESENTATION, Integer.valueOf(numberPresentation));
values.put(TYPE, Integer.valueOf(callType));
...
result = addEntryAndRemoveExpiredEntries(context, CONTENT_URI, values);
...
}
创建了一个ContentValues然后调用addEntryAndRemoveExpiredEntries
private static Uri addEntryAndRemoveExpiredEntries(Context context, Uri uri,
ContentValues values) {
final ContentResolver resolver = context.getContentResolver();
Uri result = resolver.insert(uri, values);
resolver.delete(uri, "_id IN " +
"(SELECT _id FROM calls ORDER BY " + DEFAULT_SORT_ORDER
+ " LIMIT -1 OFFSET 500)", null);
return result;
}
最终插入数据库,而且注意还会删除掉更早之前的数据,所以通话记录中最多可有500条记录。 LIMIT -1 OFFSET 500 是指从500偏移开始直到末尾的所有数据,因为默认的order是按时间倒序的,所以就是数据库中最早的数据。
通话记录中未读字段的更新
Calls.NEW是针对所有通话记录的,Calls.IS_READ专门针对未接来电。处理在packages/apps/Dialer/src/com/android/dialer/calllog/CallLogQueryHandler.java
/** Updates all new calls to mark them as old. */
public void markNewCallsAsOld() {
if (!PermissionsUtil.hasPhonePermissions(mContext)) {
return;
}
// Mark all "new" calls as not new anymore.
StringBuilder where = new StringBuilder();
where.append(Calls.NEW);
where.append(" = 1");
ContentValues values = new ContentValues(1);
values.put(Calls.NEW, "0");
startUpdate(UPDATE_MARK_AS_OLD_TOKEN, null, TelecomUtil.getCallLogUri(mContext),
values, where.toString(), null);
}
/** Updates all missed calls to mark them as read. */
public void markMissedCallsAsRead() {
if (!PermissionsUtil.hasPhonePermissions(mContext)) {
return;
}
// Mark all "new" calls as not new anymore.
StringBuilder where = new StringBuilder();
where.append(Calls.IS_READ).append(" = 0");
where.append(" AND ");
where.append(Calls.TYPE).append(" = ").append(Calls.MISSED_TYPE);
ContentValues values = new ContentValues(1);
values.put(Calls.IS_READ, "1");
startUpdate(UPDATE_MARK_MISSED_CALL_AS_READ_TOKEN, null, Calls.CONTENT_URI, values,
where.toString(), null);
}
这两个方法在packages/apps/Dialer/src/com/android/dialer/calllog/CallLogFragment.java中被调用
private void updateOnTransition(boolean onEntry) {
if (mKeyguardManager != null && !mKeyguardManager.inKeyguardRestrictedInputMode()) {
// On either of the transitions we update the missed call and voicemail notifications.
// While exiting we additionally consume all missed calls (by marking them as read).
mCallLogQueryHandler.markNewCallsAsOld();
if (!onEntry) {
mCallLogQueryHandler.markMissedCallsAsRead();
}
CallLogNotificationsHelper.removeMissedCallNotifications(getActivity()); //还取消了未接来电的通知
CallLogNotificationsHelper.updateVoicemailNotifications(getActivity());
}
}
而updateOnTransition主要在onStop中被调用
@Override
public void onStop() {
updateOnTransition(false /* onEntry */);
super.onStop();
}
进入通话记录页面就意味着未读的标记是铁定要被置成0的了。