Android 6.0 通话记录生成保存和读取显示

来源:互联网 发布:上瘾网络剧资源 编辑:程序博客网 时间:2024/06/07 04:55

这段时间在搞一个隐私联系人功能,没办法又重新梳理了一下通话记录的生成和展现过程,顺便贴出来跟大家分享一下。

这里写图片描述

一、通话记录的保存分为七步:

1、CallsManager.java 的构造函数中

// PhoneStateListener 监听电话是否断开连接
mListeners.add(mCallLogManager);

2、CallLogManager.java

// onCallStateChanged用于监听通话状态,设置状态 OUTGOING_TYPE、MISSED_TYPE和INCOMING_TYPE
public void onCallStateChanged(Call call, int oldState, int newState) {
logCall(call, type);
}

3、CallLogManager.java —> logCall()

// 插入一个通话记录

    void logCall(Call call, int callLogType) {        final long creationTime = call.getCreationTimeMillis();        final long age = call.getAgeMillis();        final String logNumber = getLogNumber(call);        Log.d(TAG, "logNumber set to: %s", Log.pii(logNumber));        final int presentation = getPresentation(call);        final PhoneAccountHandle accountHandle = call.getTargetPhoneAccount();        // TODO(vt): Once data usage is available, wire it up here.        int callFeatures = getCallFeatures(call.getVideoStateHistory());// 插入一个通话记录        logCall(call.getCallerInfo(), logNumber, presentation, callLogType, callFeatures,                accountHandle, creationTime, age, null);    }

4、AddCallArgs —> AddCallArgs()

// CallLogAsync.AddCallArgs这个类即为管理增加通话记录的类;

 //    CallLogManager.java    private void logCall(            CallerInfo callerInfo,            String number,            int presentation,            int callType,            int features,            PhoneAccountHandle accountHandle,            long start,            long duration,            Long dataUsage) {        boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(mContext, number);        // On some devices, to avoid accidental redialing of emergency numbers, we *never* log        // emergency calls to the Call Log.  (This behavior is set on a per-product basis, based        // on carrier requirements.)        final boolean okToLogEmergencyNumber =                mContext.getResources().getBoolean(R.bool.allow_emergency_numbers_in_call_log);        // Don't log emergency numbers if the device doesn't allow it.        final boolean isOkToLogThisCall = !isEmergencyNumber || okToLogEmergencyNumber;        sendAddCallBroadcast(callType, duration);        if (isOkToLogThisCall) {            Log.d(TAG, "Logging Calllog entry: " + callerInfo + ", "                    + Log.pii(number) + "," + presentation + ", " + callType                    + ", " + start + ", " + duration);// CallLogAsync.AddCallArgs这个类即为管理增加通话记录的类;            AddCallArgs args = new AddCallArgs(mContext, callerInfo, number, presentation,                    callType, features, accountHandle, start, duration, dataUsage);            logCallAsync(args);        } else {          Log.d(TAG, "Not adding emergency call to call log.");        }    }
// AddCallArgs.java    public AsyncTask<AddCallArgs, Void, Uri[]> logCallAsync(AddCallArgs args) {        return new LogCallAsyncTask().execute(args);    }    /**     * Helper AsyncTask to access the call logs database asynchronously since database operations     * can take a long time depending on the system's load. Since it extends AsyncTask, it uses     * its own thread pool.     */    private class LogCallAsyncTask extends AsyncTask<AddCallArgs, Void, Uri[]> {        @Override        protected Uri[] doInBackground(AddCallArgs... callList) {            int count = callList.length;            Uri[] result = new Uri[count];            for (int i = 0; i < count; i++) {                AddCallArgs c = callList[i];                try {                    // May block.// 这个方法即为初始化的证据。插入的证据在这:   addCall(),即为插入DB的证据;                    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 */);                } catch (Exception e) {                    // This is very rare but may happen in legitimate cases.                    // E.g. If the phone is encrypted and thus write request fails, it may cause                    // some kind of Exception (right now it is IllegalArgumentException, but this                    // might change).                    //                    // We don't want to crash the whole process just because of that, so just log                    // it instead.                    Log.e(TAG, e, "Exception raised during adding CallLog entry.");                    result[i] = null;                }            }            return result;        }        /**         * Performs a simple sanity check to make sure the call was written in the database.         * Typically there is only one result per call so it is easy to identify which one failed.         */        @Override        protected void onPostExecute(Uri[] result) {            for (Uri uri : result) {                if (uri == null) {                    Log.w(TAG, "Failed to write call to the log.");                }            }        }    }

5、CallLog —> addCall()

// 这个方法即为初始化的证据。插入的证据在这: addCall(),即为插入DB的证据;
frameworks/base/core/java/android/provider/CallLog.java.

// 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) {            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 = "";                }            }            // accountHandle information            String accountComponentString = null;            String accountId = null;            if (accountHandle != null) {                accountComponentString = accountHandle.getComponentName().flattenToString();                accountId = accountHandle.getId();            }            ContentValues values = new ContentValues(6);            values.put(NUMBER, number);            values.put(NUMBER_PRESENTATION, Integer.valueOf(numberPresentation));            values.put(TYPE, Integer.valueOf(callType));            values.put(FEATURES, features);            values.put(DATE, Long.valueOf(start));            values.put(DURATION, Long.valueOf(duration));            if (dataUsage != null) {                values.put(DATA_USAGE, dataUsage);            }            values.put(PHONE_ACCOUNT_COMPONENT_NAME, accountComponentString);            values.put(PHONE_ACCOUNT_ID, accountId);            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.contactIdOrZero > 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.contactIdOrZero),                                    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.contactIdOrZero) },                            null);                }// 更新通话记录操作                if (cursor != null) {                    try {                        if (cursor.getCount() > 0 && cursor.moveToFirst()) {                            final String dataId = cursor.getString(0);                            updateDataUsageStatForData(resolver, dataId);                            if (duration >= MIN_DURATION_FOR_NORMALIZED_NUMBER_UPDATE_MS                                    && callType == Calls.OUTGOING_TYPE                                    && TextUtils.isEmpty(ci.normalizedNumber)) {                                updateNormalizedNumber(context, resolver, dataId, number);                            }                        }                    } finally {                        cursor.close();                    }                }            }            /// M: new feature:IP dial enhancement start @{            String ipPrefix = null;            ipPrefix = Settings.System.getString(resolver, "ipprefix" + accountId);            if (null != ipPrefix && null != number && number.startsWith(ipPrefix)                    && !number.equals(ipPrefix) && callType == Calls.OUTGOING_TYPE) {                values.put(IP_PREFIX, ipPrefix);                String tmpNumber = number.substring(ipPrefix.length(), number.length());                values.put(NUMBER, tmpNumber);            }            /// @}            Uri result = null;            if (addForAllUsers) {                // Insert the entry for all currently running users, in order to trigger any                // ContentObservers currently set on the call log.                final UserManager userManager = (UserManager) context.getSystemService(                        Context.USER_SERVICE);                List<UserInfo> users = userManager.getUsers(true);                final int currentUserId = userManager.getUserHandle();                final int count = users.size();                for (int i = 0; i < count; i++) {                    final UserInfo user = users.get(i);                    final UserHandle userHandle = user.getUserHandle();                    if (userManager.isUserRunning(userHandle)                            && !userManager.hasUserRestriction(UserManager.DISALLOW_OUTGOING_CALLS,                                    userHandle)                            && !user.isManagedProfile()) {// 插入通话记录操作                        Uri uri = addEntryAndRemoveExpiredEntries(context,                                ContentProvider.maybeAddUserId(CONTENT_URI, user.id), values);                        if (user.id == currentUserId) {                            result = uri;                        }                    }                }            } else {                result = addEntryAndRemoveExpiredEntries(context, CONTENT_URI, values);            }            return result;        }

6、CallLog —> addEntryAndRemoveExpiredEntries()

// 插入通话记录操作

// CallLog.java 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;        }

7、CallLog —> resolver.insert(uri, values)

// 通话记录插入操作

二、通话记录的显示总共25步

这里写图片描述

1、CallLogFragment —>onResume()

// 每次切换导航栏、切换语言、点击Home键都会重新刷新通话记录

// CallLogFragment.java    @Override    public void onResume() {        super.onResume();// 用于查询数据库        refreshData();    }

2、CallLogFragment —>refreshData()

// 申请更新数据用于显示

 // CallLogFragment.java // 申请更新数据用于显示    private void refreshData() {// 是否需要刷新数据        if (mRefreshDataRequired) {/* 在接触信息缓存中的所有条目都会被记录出来,这样他们就会被人看到再次显示一次。*/            mAdapter.invalidateCache();            startCallsQuery();            startVoicemailStatusQuery();            updateOnEntry();            mRefreshDataRequired = false;        /// M: for ALPS01772987 @{        // need to update data without re-query        } else {            mAdapter.notifyDataSetChanged();        }        /// @}        if (mNeedAccountFilter) {            updateNotice();        }    }

3、CallLogAdapter—>invalidateCache()

/* 在接触信息缓存中的所有条目都会被记录出来,这样他们就会被人看到
再次显示一次。*/

// CallLogAdapter.java public void invalidateCache() {        mContactInfoCache.expireAll();        // Restart the request-processing thread after the next draw.        stopRequestProcessing();        unregisterPreDrawListener();    }

4、CallLogFragment —>startCallsQuery()

// 读取sim卡过滤设置、通话类型设置(来电?去电?未接?全部? ),开始查询

//  CallLogFragment.java //   读取sim卡过滤设置、通话类型设置,开始查询public void startCallsQuery() {// 正在加载联系人,此时联系人列表不显示为 空        mAdapter.setLoading(true);        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getActivity());// 通话类型:来电?去电?未接?全部?         mCallTypeFilter = prefs.getInt(Constants.TYPE_FILTER_PREF, CallLogQueryHandler.CALL_TYPE_ALL);        mCallLogQueryHandler.fetchCalls(mCallTypeFilter, mDateLimit, getAccountFilterId());    }

5、CallLogAdapter —>setLoading(true)

// 正在加载联系人,此时联系人列表不显示为 空

//   CallLogAdapter.java public void setLoading(boolean loading) {        mLoading = loading;    }

6、CallLogQueryHandler —>fetchCalls()

// 获取列表中的通话记录

// CallLogQueryHandler.javapublic void fetchCalls(int callType, long newerThan, String accountId) {        cancelFetch();        fetchCalls(QUERY_CALLLOG_TOKEN, callType, false /* newOnly */, newerThan, accountId);    }

注:一步一步将添加查询条件,将查询请求提交给ContentProvider。

7、NoNullCursorAsyncQueryHandler—>startQuery

// 设置查询Uri,

// CallLogQueryHandler.java    /** Fetches the list of calls in the call log. */    private void fetchCalls(int token, int callType, boolean newOnly,            long newerThan, String accountId) {        // We need to check for NULL explicitly otherwise entries with where READ is NULL        // may not match either the query or its negation.        // We consider the calls that are not yet consumed (i.e. IS_READ = 0) as "new".        StringBuilder where = new StringBuilder();        List<String> selectionArgs = Lists.newArrayList();        if (newOnly) {            where.append(Calls.NEW);            where.append(" = 1");        }        if (callType > CALL_TYPE_ALL) {            if (where.length() > 0) {                where.append(" AND ");            }            // Add a clause to fetch only items of type voicemail.            where.append(String.format("(%s = ?)", Calls.TYPE));            // Add a clause to fetch only items newer than the requested date            selectionArgs.add(Integer.toString(callType));        }        if (newerThan > 0) {            if (where.length() > 0) {                where.append(" AND ");            }            where.append(String.format("(%s > ?)", Calls.DATE));            selectionArgs.add(Long.toString(newerThan));        }        if (!PhoneAccountInfoHelper.FILTER_ALL_ACCOUNT_ID.equals(accountId)) {            if (where.length() > 0) {                where.append(" AND ");            }            // query the Call Log by account id            where.append(String.format("(%s = ?)", Calls.PHONE_ACCOUNT_ID));            selectionArgs.add(accountId);        }        /// M: for Plug-in @{        ExtensionManager.getInstance().getCallLogExtension().appendQuerySelection(callType, where, selectionArgs);        /// @}        final int limit = (mLogLimit == -1) ? NUM_LOGS_TO_DISPLAY : mLogLimit;        final String selection = where.length() > 0 ? where.toString() : null;        Uri uri = Calls.CONTENT_URI_WITH_VOICEMAIL.buildUpon()                .appendQueryParameter(Calls.LIMIT_PARAM_KEY, Integer.toString(limit))                .build();        /// M: [Union Query] MTK CallLog Query @{        if (DialerFeatureOptions.CALL_LOG_UNION_QUERY) {            // change CallLog query data source to calls join data view            uri = Uri.parse("content://call_log/callsjoindataview").buildUpon()                    .appendQueryParameter(Calls.ALLOW_VOICEMAILS_PARAM_KEY, "true")                    .appendQueryParameter(Calls.LIMIT_PARAM_KEY, Integer.toString(limit))                    .build();        }        LogUtils.d(TAG, "fetchCalls(), queryUri = " + uri.toString() + ", selection = " + selection                + ", selectionArgs = " + selectionArgs);        /// @}// 设置查询Uri,        startQuery(token, null, uri,                CallLogQuery._PROJECTION, selection, selectionArgs.toArray(EMPTY_STRING_ARRAY),                Calls.DEFAULT_SORT_ORDER);    }
// NoNullCursorAsyncQueryHandler.java    @Override    public void startQuery(int token, Object cookie, Uri uri, String[] projection, String selection,            String[] selectionArgs, String orderBy) {        final CookieWithProjection projectionCookie = new CookieWithProjection(cookie, projection);        super.startQuery(token, projectionCookie, uri, projection, selection, selectionArgs,                orderBy);    }

注:CallLogQueryHandlerEx、NoNullCursorAsyncQueryHandler抽象类、AsyncQueryHandler抽象类是继承关系,继承自Handler

9、AsyncQueryHandler—>super.startQuery

//AsyncQueryHandler是Framework中提供的异步查询类,开始一个异步查询
定义在\frameworks\base\core\java\android\content,step10将查询请求交给它

//开始一个异步查询public void startQuery(int token, Object cookie, Uri uri,            String[] projection, String selection, String[] selectionArgs,            String orderBy) {        // Use the token as what so cancelOperations works properly//mWorkerThreadHandler是WorkerHandler的对象,也是一个Handler,与工作线程通信          Message msg = mWorkerThreadHandler.obtainMessage(token);        msg.arg1 = EVENT_ARG_QUERY;        WorkerArgs args = new WorkerArgs();//this即AsyncQueryHandler,用于工作线程返回查询结果Cursor          args.handler = this;        args.uri = uri;        args.projection = projection;        args.selection = selection;        args.selectionArgs = selectionArgs;        args.orderBy = orderBy;        args.cookie = cookie;        msg.obj = args;//查询将在工作线程中进行         mWorkerThreadHandler.sendMessage(msg);    }

10、WorkerHandler—>mWorkerThreadHandler.sendMessage(msg)

//mWorkerThreadHandler是WorkerHandler的对象,也是一个Handler,与工作线程通信查询将在工作线程中进行
注:11~14步 工作线程将查询结果返回给AsyncQueryHandler的handleMessage()处理。

11、WorkerHandler—>handleMessage()

// 工作线程将查询结果返回给AsyncQueryHandler的handleMessage()处理。

12、WorkerHandler—>resolver.query()

// 使用query查询获得cursor,

13、WorkerHandler—>reply.sendToTarget()

1)将查询的cursor 赋值 args.result = cursor;
2)//args.handler就是上文提到的this ,this即AsyncQueryHandler,用于工作线程返回查询结果Cursor
Message reply = args.handler.obtainMessage(token);
// 发送message
reply.sendToTarget();

// AsyncQueryHandler.java    protected class WorkerHandler extends Handler {        public WorkerHandler(Looper looper) {            super(looper);        }        @Override        public void handleMessage(Message msg) {            final ContentResolver resolver = mResolver.get();            if (resolver == null) return;            WorkerArgs args = (WorkerArgs) msg.obj;            int token = msg.what;            int event = msg.arg1;            switch (event) {                case EVENT_ARG_QUERY:                    Cursor cursor;                    try {/*selection表示查找的列,selectionArgs为where条件*/                        cursor = resolver.query(args.uri, args.projection,                                args.selection, args.selectionArgs,                                args.orderBy);                        // Calling getCount() causes the cursor window to be filled,                        // which will make the first access on the main thread a lot faster.                        if (cursor != null) {                            cursor.getCount();                        }                    } catch (Exception e) {                        Log.w(TAG, "Exception thrown during handling EVENT_ARG_QUERY", e);                        cursor = null;                    }//查询结果cursor                      args.result = cursor;                    break;            }            // passing the original token value back to the caller            // on top of the event values in arg1.//args.handler就是上文提到的this             Message reply = args.handler.obtainMessage(token);            reply.obj = args;            reply.arg1 = msg.arg1;            if (localLOGV) {                Log.d(TAG, "WorkerHandler.handleMsg: msg.arg1=" + msg.arg1                        + ", reply.what=" + reply.what);            }/*你好,Message.sendToTarget() ---> Message.target.sendMessage(), 这里target是个Handler,所以你说的消息是发送到这个Message内部持有的Handler对象,加入他的MessageQueue。所以你要找到赋给该Message Handler的地方,在它的handleMessage里就可以看到处理了。至于在什么地方赋的值,可以看在什么地方调用了Message.obtain(Hanlder)或者Handler.obtainMessage()这类的函数*/            reply.sendToTarget();        }    }

14、NoNullCursorAsyncQueryHandler—>onQueryComplete()

//通话记录为空,创建一个空的cursor返回
查询完成,返回cursor,判断cursor是否为空。

// NoNullCursorAsyncQueryHandler.java    @Override    protected final void onQueryComplete(int token, Object cookie, Cursor cursor) {        CookieWithProjection projectionCookie = (CookieWithProjection) cookie;        super.onQueryComplete(token, projectionCookie.originalCookie, cursor);//通话记录为空,创建一个空的cursor返回          if (cursor == null) {            cursor = new EmptyCursor(projectionCookie.projection);        }        onNotNullableQueryComplete(token, projectionCookie.originalCookie, cursor);    }

15、CallLogQueryHandler—>onNotNullableQueryComplete()

1)、QUERY_CALLLOG_TOKEN 查询的标记,以从调用通话记录中获取旧的条目
2)、QUERY_SEARCH_TOKEN 用于通话记录全局搜索

// CallLogQueryHandler.java    @Override  protected synchronized void onNotNullableQueryComplete(int token, Object cookie, Cursor cursor) {        if (cursor == null) {            return;        }        try {            /// M: [CallLog Search] For call log global search. @{            /*1)、QUERY_CALLLOG_TOKEN 查询的标记,以从调用通话记录中获取旧的条目2)、QUERY_SEARCH_TOKEN 用于通话记录全局搜索            */            if (token == QUERY_CALLLOG_TOKEN || token == QUERY_SEARCH_TOKEN) {            /// @}                if (updateAdapterData(cursor)) {                    cursor = null;                }            } else if (token == QUERY_VOICEMAIL_STATUS_TOKEN) {                updateVoicemailStatus(cursor);            } else {                Log.w(TAG, "Unknown query completed: ignoring: " + token);            }        } finally {            if (cursor != null) {                cursor.close();            }        }    }

16、CallLogQueryHandler—>updateAdapterData()

// 更新adapter 在calllogfragment中用于显示更新后的数据

// 更新adapter 在calllogfragment中用于显示更新后的数据    private boolean updateAdapterData(Cursor cursor) {        final Listener listener = mListener.get();        if (listener != null) {            return listener.onCallsFetched(cursor);        }        return false;    }

17、CallLogFragment—>listener.onCallsFetched()

// 当通话记录列表被获取或者更新时被CallLogQueryHandler 调用,将结果cursor返回给CallLogFragment。

//   CallLogFragment.java// 被CallLogQueryHandler 调用当通话记录列表被获取或者更新时    @Override    public boolean onCallsFetched(Cursor cursor) {// 联系人加载完毕,此时通话记录显示更新后的联系人        mAdapter.setLoading(false);//更改CallLogListAdapter的cursor,刷新ListView        mAdapter.changeCursor(cursor);        // This will update the state of the "Clear call log" menu item.        getActivity().invalidateOptionsMenu();        if (mScrollToTop) {            final ListView listView = getListView();            if (listView.getFirstVisiblePosition() > 5) {                listView.setSelection(5);            }            // Workaround for framework issue: the smooth-scroll doesn't            // occur if setSelection() is called immediately before.            mHandler.post(new Runnable() {               @Override               public void run() {                   if (getActivity() == null || getActivity().isFinishing()) {                       return;                   }                   listView.smoothScrollToPosition(0);               }            });            mScrollToTop = false;        }        mCallLogFetched = true;        destroyEmptyLoaderIfAllDataFetched();        return true;    }

注:step18~step19,将结果cursor返回给CallLogFragment。

18、CallLogFragment—>mAdapter.setLoading(false)

// 联系人加载完毕,此时通话记录显示更新后的联系人

19、CallLogAdapter—>mAdapter.changeCursor()

//更改CallLogListAdapter的cursor,刷新ListView

20~24、主要是处理通话记录的分组显示。

20、GroupingListAdapter—>super.changeCursor

// GroupingListAdapter.javapublic void changeCursor(Cursor cursor) {        if (cursor == mCursor) {            return;        }        if (mCursor != null) {            mCursor.unregisterContentObserver(mChangeObserver);            mCursor.unregisterDataSetObserver(mDataSetObserver);            mCursor.close();        }        mCursor = cursor;        resetCache();        findGroups();        if (cursor != null) {            cursor.registerContentObserver(mChangeObserver);            cursor.registerDataSetObserver(mDataSetObserver);            mRowIdColumnIndex = cursor.getColumnIndexOrThrow("_id");            notifyDataSetChanged();        } else {            // notify the observers about the lack of a data set            notifyDataSetInvalidated();        }    }

21、CallLogAdapter—>addGroups()

//  CallLogAdapter.java @Override protected void addGroups(Cursor cursor) {        mCallLogGroupBuilder.addGroups(cursor);    }

22、GroupingListAdapter—>findGroups()

设置mGroupMetadata初始化大小16,用于记录一个Group(ListView的一个item)的开始位置和大小(包含的通话记录数目)

// GroupingListAdapter.java    private void findGroups() {        mGroupCount = 0;        mGroupMetadata = new long[GROUP_METADATA_ARRAY_INITIAL_SIZE];        if (mCursor == null) {            return;        }        addGroups(mCursor);    }

23、CallLogGroupBuilder—>mCallLogGroupBuilder.addGroups(cursor)()

具体的分组规则和分组过程

// CallLogGroupBuilder.java  public void addGroups(Cursor cursor) {        final int count = cursor.getCount();        if (count == 0) {            return;        }        // Clear any previous day grouping information.        mGroupCreator.clearDayGroups();        // Get current system time, used for calculating which day group calls belong to.        long currentTime = System.currentTimeMillis();        int currentGroupSize = 1;        cursor.moveToFirst();        // The number of the first entry in the group.        String firstNumber = cursor.getString(CallLogQuery.NUMBER);        // This is the type of the first call in the group.        int firstCallType = cursor.getInt(CallLogQuery.CALL_TYPE);        // The account information of the first entry in the group.        String firstAccountComponentName = cursor.getString(CallLogQuery.ACCOUNT_COMPONENT_NAME);        String firstAccountId = cursor.getString(CallLogQuery.ACCOUNT_ID);        // Determine the day group for the first call in the cursor.        final long firstDate = cursor.getLong(CallLogQuery.DATE);        final long firstRowId = cursor.getLong(CallLogQuery.ID);        int currentGroupDayGroup = getDayGroup(firstDate, currentTime);        mGroupCreator.setDayGroup(firstRowId, currentGroupDayGroup);        while (cursor.moveToNext()) {            // The number of the current row in the cursor.            final String currentNumber = cursor.getString(CallLogQuery.NUMBER);            final int callType = cursor.getInt(CallLogQuery.CALL_TYPE);            final String currentAccountComponentName = cursor.getString(                    CallLogQuery.ACCOUNT_COMPONENT_NAME);            final String currentAccountId = cursor.getString(CallLogQuery.ACCOUNT_ID);            final boolean sameNumber = equalNumbers(firstNumber, currentNumber);            final boolean sameAccountComponentName = Objects.equals(                    firstAccountComponentName,                    currentAccountComponentName);            final boolean sameAccountId = Objects.equals(                    firstAccountId,                    currentAccountId);            final boolean sameAccount = sameAccountComponentName && sameAccountId;            final boolean shouldGroup;            final long currentCallId = cursor.getLong(CallLogQuery.ID);            final long date = cursor.getLong(CallLogQuery.DATE);            if (!sameNumber || !sameAccount) {                // Should only group with calls from the same number.                shouldGroup = false;            } else if (firstCallType == Calls.VOICEMAIL_TYPE) {                // never group voicemail.                shouldGroup = false;            } else {                // Incoming, outgoing, and missed calls group together.                shouldGroup = callType != Calls.VOICEMAIL_TYPE;            }            if (shouldGroup) {                // Increment the size of the group to include the current call, but do not create                // the group until we find a call that does not match.                currentGroupSize++;            } else {                // The call group has changed, so determine the day group for the new call group.                // This ensures all calls grouped together in the call log are assigned the same                // day group.                currentGroupDayGroup = getDayGroup(date, currentTime);                // Create a group for the previous set of calls, excluding the current one, but do                // not create a group for a single call.                if (currentGroupSize > 1) {                    addGroup(cursor.getPosition() - currentGroupSize, currentGroupSize);                }                // Start a new group; it will include at least the current call.                currentGroupSize = 1;                // The current entry is now the first in the group.                firstNumber = currentNumber;                firstCallType = callType;                firstAccountComponentName = currentAccountComponentName;                firstAccountId = currentAccountId;            }            // Save the day group associated with the current call.            mGroupCreator.setDayGroup(currentCallId, currentGroupDayGroup);        }        // If the last set of calls at the end of the call log was itself a group, create it now.        if (currentGroupSize > 1) {            addGroup(count - currentGroupSize, currentGroupSize);        }    }

24、CallLogGroupBuilder—>mGroupCreator.setDayGroup()

用于跟踪每一个呼叫所属的天组,同一天组作为第一次呼叫组,分为今天,昨天,上周,和其他几种分组,在setDayGroup()设置getDayGroup()中获取

25、CallLogAdapter —> bindView()

通话记录ListView和Adapter的数据绑定是在GroupingListAdapter中的getView()方法中,具体实现放在CallLogAdapter的bindView()实现,最后,将通话记录加载到列表中

1 0
原创粉丝点击