1,通话记录
1.1 初始化
在Dialer中,通话记录信息都是通过CallLogActivity 显示,实际上,真正完成的是CallLogFragment 。CallLogActivity的内部类
ViewPagerAdapter的getItem方法如下,
public Fragment getItem(int position) {
switch (getRtlPosition(position)) {
case TAB_INDEX_ALL:
return new CallLogFragment(CallLogQueryHandler.CALL_TYPE_ALL);
case TAB_INDEX_MISSED:
return new CallLogFragment(Calls.MISSED_TYPE);
}
throw new IllegalStateException("No fragment at position " + position);
}
查询的数据库:contacts2.calls
通话记录没有搜索,在onCreateView函数里根据不同的参数直接查询。
查询的时间顺序是有近到远。
CallLogFragment的构造方法如下,
public CallLogFragment(int filterType, int logLimit, long dateLimit) {
mCallTypeFilter = filterType;//查询通话记录的类型
mLogLimit = logLimit;
mDateLimit = dateLimit;
}
通话记录主要包括以下类型:
所有通话,未接来电,所有外拨电话,所有来电,黑名单来电。
CallLogQueryHandler对应的定义如下,
private static final int INCOMING_IMS_TYPE = 5;
private static final int OUTGOING_IMS_TYPE = 6;
private static final int MISSED_IMS_TYPE = 7;
•••
CallLogFragment的onCreate方法主要逻辑如下,
final Activity activity = getActivity();//获取所在的Activity对象
//获取进程的ContentResolver对象
final ContentResolver resolver = activity.getContentResolver();
String currentCountryIso = GeoUtil.getCurrentCountryIso(activity);
//构造CallLogQueryHandler对象
mCallLogQueryHandler = new CallLogQueryHandler(activity, resolver, this, mLogLimit);
//锁屏管理
mKeyguardManager =
(KeyguardManager) activity.getSystemService(Context.KEYGUARD_SERVICE);
//注册通话记录数据库监听
resolver.registerContentObserver(CallLog.CONTENT_URI, true, mCallLogObserver);
//注册联系人数据库监听
resolver.registerContentObserver(ContactsContract.Contacts.CONTENT_URI, true,
mContactsObserver);
resolver.registerContentObserver(Status.CONTENT_URI, true, mVoicemailStatusObserver);
setHasOptionsMenu(true);//设置菜单
CallLogFragment的onCreateView方法主要逻辑如下,
1,获取RecyclerView布局,
mRecyclerView = (RecyclerView) view.findViewById(R.id.recycler_view);
mRecyclerView.setHasFixedSize(true);
mLayoutManager = new LinearLayoutManager(getActivity());
mRecyclerView.setLayoutManager(mLayoutManager);
2,构造RecyclerView的Adapter
mAdapter = ObjectFactory.newCallLogAdapter(getActivity(),this,
new ContactInfoHelper(getActivity(), currentCountryIso), mVoicemailPlaybackPresenter,
isShowingRecentsTab);
mRecyclerView.setAdapter(mAdapter);
3,调用fetchCalls方法开始查询通话记录
fetchCalls();
1.2 查询通话记录
CallLogFragment的fetchCalls调用流程图如下,
fetchCalls方法如下,
public void fetchCalls() {
mCallLogQueryHandler.fetchCalls(mCallTypeFilter, mDateLimit);
}
CallLogQueryHandler有不同参数的fetchCalls方法,最后的fetchCalls方法主要逻辑如下,
1,构造查询语句,
StringBuilder where = new StringBuilder();
List<String> selectionArgs = Lists.newArrayList();
// Ignore voicemails marked as deleted
where.append(Voicemails.DELETED);
where.append(" = 0");
if (newOnly) {
where.append(" AND ");
where.append(Calls.NEW);
where.append(" = 1");
}
•••
2,根据通话记录查询类型构造查询参数,
if (callType > CALL_TYPE_ALL) {
if (where.length() > 0) {
where.append(" AND ");
}
if ((callType == Calls.INCOMING_TYPE) || (callType == Calls.OUTGOING_TYPE)
|| (callType == Calls.MISSED_TYPE)) {
where.append(String.format("(%s = ? OR %s = ?)",
Calls.TYPE, Calls.TYPE));
} else {
// 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 (callType == Calls.INCOMING_TYPE) {
selectionArgs.add(Integer.toString(INCOMING_IMS_TYPE));
} else if (callType == Calls.OUTGOING_TYPE) {
selectionArgs.add(Integer.toString(OUTGOING_IMS_TYPE));
} else if (callType == Calls.MISSED_TYPE) {
selectionArgs.add(Integer.toString(MISSED_IMS_TYPE));
}
•••
3,获取URI
final int limit = (mLogLimit == -1) ? NUM_LOGS_TO_DISPLAY : mLogLimit;
final String selection = where.length() > 0 ? where.toString() : null;
Uri uri = TelecomUtil.getCallLogUri(mContext).buildUpon()
.appendQueryParameter(Calls.LIMIT_PARAM_KEY, Integer.toString(limit)).build();
4,调用startQuery方法进行查询,
startQuery(token, null, uri, CallLogQuery._PROJECTION, selection,
selectionArgs.toArray(EMPTY_STRING_ARRAY), Calls.DEFAULT_SORT_ORDER);
父类NoNullCursorAsyncQueryHandler的startQuery方法如下,
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);
}
直接调用父类AsyncQueryHandler的startQuery方法进行异步查询, AsyncQueryHandler的原理在此不论述了。
只需要知道的是AsyncQueryHandler查询完成之后会回调onQueryComplete方法。
NoNullCursorAsyncQueryHandler的onQueryComplete方法如下,
protected final void onQueryComplete(int token, Object cookie, Cursor cursor) {
CookieWithProjection projectionCookie = (CookieWithProjection) cookie;
super.onQueryComplete(token, projectionCookie.originalCookie, cursor);
if (cursor == null) {
cursor = new EmptyCursor(projectionCookie.projection);
}
onNotNullableQueryComplete(token, projectionCookie.originalCookie, cursor);
}
onNotNullableQueryComplete方法是一个abstract方法,子类CallLogQueryHandler的实现如下,
if (token == QUERY_CALLLOG_TOKEN) {
if (updateAdapterData(cursor)) {
cursor = null;
}
•••
如果是普通的通话记录,就调用updateAdapterData方法更新数据。
updateAdapterData方法如下,
private boolean updateAdapterData(Cursor cursor) {
final Listener listener = mListener.get();
if (listener != null) {
return listener.onCallsFetched(cursor);
}
return false;
}
回调监听器的onCallsFetched方法,当然是在CallLogFragment实现。
监听器Listener是CallLogQueryHandler的内部接口,仅有2个方法,
public interface Listener {
/** Called when {@link CallLogQueryHandler#fetchVoicemailStatus()} completes. */
void onVoicemailStatusFetched(Cursor statusCursor);
/**
* Called when {@link CallLogQueryHandler#fetchCalls(int)} complete.
* Returns true if takes ownership of cursor.
*/
boolean onCallsFetched(Cursor combinedCursor);
}
mListener是一个WeakReference组,
private final WeakReference<Listener> mListener;
在CallLogQueryHandler的构造方法中初始化,
public CallLogQueryHandler(Context context, ContentResolver contentResolver, Listener listener,
int limit) {
super(contentResolver);
mContext = context.getApplicationContext();
mListener = new WeakReference<Listener>(listener);
mLogLimit = limit;
}
在CallLogFragment的onCallsFetched方法会完成通话记录的更新显示。
1.3 更新显示
CallLogFragment的onCallsFetched方法主要逻辑如下,
mAdapter.changeCursor(cursor);
mAdapter 是CallLogAdapter对象, 并且继承于GroupingListAdapter, GroupingListAdapter定义如下,
abstract class GroupingListAdapter extends RecyclerView.Adapter {
其中, GroupingListAdapter实现了RecyclerView.Adapter 的getItemCount方法,
CallLogAdapter实现了RecyclerView.Adapter的onCreateViewHolder/ onBindViewHolder方法。
GroupingListAdapter的changeCursor方法主要逻辑如下,
1,为mCursor变量赋值,
mCursor = cursor;
2,调用findGroups方法对查询到的通话记录分组,
findGroups();
3,更新界面。
notifyDataSetChanged();
notifyDataSetChanged原理在此就不论述了,总之会调用getItemCount/onCreateViewHolder/ onBindViewHolder方法更新界面。
1.3.1 分组
看通话记录界面,可以看到:
1,通话记录分为三类:今天,昨天,更早。如何分类的?
2,相邻的同一号码为一组显示。如何做到的?
查询完之后,会做两件事情,分组(分为今天,昨天以及更早),相邻的相同号码的通话记录分为一组。两件事情在两个不同的类中进行,但是同时进行。
首先在CallLogGroupBuilder类中的addGroups进行分组,分为今天,昨天以及更早。分组的依据是将通话时的时间和当前的时间进行对比。
int currentGroupDayGroup = getDayGroup(firstDate, currentTime);
mGroupCreator.setDayGroup(firstRowId, currentGroupDayGroup);
然后调用CallLogAdapter 的setDayGroup 函数将相关信息存储在
mDayGroups(HashMap)中。在bindCallLogListViewHolder函数显示时,在该HashMap中查询当前和前一个Cursor的分组信息,
如果不相同,就显示一个分隔组(昨天或者更早)
int currentGroup = getDayGroupForCall(views.rowId);
int previousGroup = getPreviousDayGroup(c);
if (currentGroup != previousGroup) {
views.dayGroupHeader.setVisibility(View.VISIBLE);
views.dayGroupHeader.setText(getGroupDescription(currentGroup));
} else {
views.dayGroupHeader.setVisibility(View.GONE);
}
不仅如此,在addGroups函数中,会比较相邻号码等相关信息是否相同,如果相同就是一个组,如果不同就新建一个组。
然后调用GroupingListAdapter的addGroup函数将这些分组信息保存在64位的mGroupMetadata数组中。其中,高位表示分组的大小,
低位表示分组的起始位置,这两个信息很重要,是显示的基础。
long metadata = ((long)size << 32) | cursorPosition;
mGroupMetadata[mGroupCount++] = metadata;
比如:通话记录(3,1,2,2)共8条通话记录,分为4个组,保存的信息为(0,3),(3,1),(4,2),
(6,2)。GroupingListAdapter中的getItemCount()函数根据相关信息,返回的结果为通话记录的组数,而不是单条的通话记录,
这和listview的不一样。这样就回答了上面的两个疑惑。
1.3.2 onCreateViewHolder
CallLogAdapter的onCreateViewHolder方法如下,
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == VIEW_TYPE_SHOW_CALL_HISTORY_LIST_ITEM) {
return ShowCallHistoryViewHolder.create(mContext, parent);
} else if (viewType == VIEW_TYPE_VOICEMAIL_PROMO_CARD) {
return createVoicemailPromoCardViewHolder(parent);
}
return createCallLogEntryViewHolder(parent);
}
createCallLogEntryViewHolder的方法如下,
private ViewHolder createCallLogEntryViewHolder(ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(mContext);
View view = inflater.inflate(R.layout.call_log_list_item, parent, false);
CallLogListItemViewHolder viewHolder = CallLogListItemViewHolder.create(
view, mContext, mExpandCollapseListener, mTelecomCallLogCache,
mCallLogListItemHelper, mVoicemailPlaybackPresenter);
viewHolder.callLogEntryView.setTag(viewHolder);
viewHolder.callLogEntryView.setAccessibilityDelegate(mAccessibilityDelegate);
viewHolder.primaryActionView.setOnCreateContextMenuListener(mOnCreateContextMenuListener);
viewHolder.primaryActionView.setTag(viewHolder);
return viewHolder;
}
由此,一条通话记录就对应一个CallLogListItemViewHolder对象。
1.3.3 onBindViewHolder
CallLogAdapter的onBindViewHolder方法会调用bindCallLogListViewHolder方法加载每条通话记录的信息,
bindCallLogListViewHolder方法的主要逻辑如下,
1,获取通话记录组中的第一个Cursor以及该组中的通话记录条数。
Cursor c = (Cursor) getItem(position);
if (c == null) {
return;
}
int count = getGroupSize(position);
2,依次将号码等信息封装在ContactInfo, PhoneCallDetails以及CallLogListItemViewHolder类中。
3,控制是否显示分组,调用CallLogListItemViewHolder的showActions()函数是否显示新建联系人等信息(根据ContactInfo来决定)。
4,调用CallLogListItemViewHolder的setPhoto函数显示图标以及姓名等信息。
5,PhoneCallDetailsViews详细的显示PhoneCallDetails中的通话记录信息(通话时间以及归属地等等),并且PhoneCallDetailsViews
是包含于CallLogListItemViewHolder中的。