Android通话记录数据库表分析

数据库

实现在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的了。
 
 
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值