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()实现,最后,将通话记录加载到列表中
- Android 6.0 通话记录生成保存和读取显示
- android 读取手机通话记录
- Android端通话记录读取
- ContentProvider读取通讯录和通话记录
- android 通话记录显示名称不对
- android 保存具有不同前缀的同一号码分别为A和B,用其中一个呼叫,通话记录一直显示另一个联系人名字的问题
- android 保存具有不同前缀的同一号码分别为A和B,用其中一个呼叫,通话记录一直显示另一个联系人名字的问题
- android 保存具有不同前缀的同一号码分别为A和B,用其中一个呼叫,通话记录一直显示另一个联系人名字的问题
- 保存通话记录
- android 保存和读取文件
- Android文件保存和读取
- android 保存和读取文件
- Android开发合并相同通话记录并显示通话记录条数
- opencv 图像的读取显示和保存
- 视频的读取,显示和保存
- android中读取联系人和通话记录
- android中读取联系人和通话记录
- android中根据电话号码读取通话记录表
- 哈夫曼编码(Huffman)
- 汉诺塔
- java基础Day03
- 韦东山毕业班视频git安装不成功解决办法
- sqlserver+asp.net+devextreme从零开始(1)
- Android 6.0 通话记录生成保存和读取显示
- LocalBroadcastManager 的使用
- tjut 4862
- 输入三个正整数进行从小到大的排序
- PHPCMS多栏目调用推荐位
- Android6.0 数据分享之Content Provider原理
- HDOJ 1789 Doing Homework again
- C#实现数字字符串左补齐0的两种方法
- java(九九乘法表)