4.3 通话记录保存
在通话到结束的整个过程中,为了保存通话信息,包括通话号码,通话时间等。在通话完全结束后,就会将这些信息插入到通话记录的数据库中。
1,插入通话记录
在前面通话状态更新一节中,Telecom进程的CallsManager的setCallState方法会调用监听器的onCallStateChanged方法,
for (CallsManagerListener listener : mListeners) {
•••
listener.onCallStateChanged(call, oldState, newState);
•••
}
当然,监听器不止一个对象。在CallsManager的构造方法中,会将CallsManagerListener保存在mListeners 中,
mListeners.add(statusBarNotifier);
mListeners.add(mCallLogManager);
mListeners.add(mPhoneStateBroadcaster);
mListeners.add(mInCallController);
mListeners.add(mRinger);
•••
这个小节主要论述通话记录插入数据库的过程,因此,主要看CallLogManager。
CallLogManager的onCallStateChanged方法调用流程图如下,
onCallStateChanged方法逻辑如下,
1,获取通话状态以及相关信息,
int disconnectCause = call.getDisconnectCause().getCode(); //通话断开原因
//判断通话是否结束
boolean isNewlyDisconnected =
newState == CallState.DISCONNECTED || newState == CallState.ABORTED;
//通话取消
boolean isCallCanceled = isNewlyDisconnected && disconnectCause == DisconnectCause.CANCELED;
2,只有在通话结束的情况下才会添加通话信息,
if (isNewlyDisconnected &&
(oldState != CallState.SELECT_PHONE_ACCOUNT &&
!call.isConference() &&
!isCallCanceled)) {
3,获取通话的类型,是去电,未接来电还是来电,一共只有三种类型,
int type;
if (!call.isIncoming()) {
type = Calls.OUTGOING_TYPE;
} else if (disconnectCause == DisconnectCause.MISSED) {
type = Calls.MISSED_TYPE;
} else {
type = Calls.INCOMING_TYPE;
}
CallLog的内部类Calls中定义了4中类型的通话,对应的值如下,
public static final int INCOMING_TYPE = 1; //来电
public static final int OUTGOING_TYPE = 2; //去电
public static final int MISSED_TYPE = 3; //未接来电
public static final int VOICEMAIL_TYPE = 4; //语音邮箱电话
4,最后调用logCall方法,
logCall(call, type);
logCall方法主要逻辑如下,
1,首先获取通话信息,比如通话号码,通话时间等等,
final long creationTime = call.getCreationTimeMillis();
final long age = call.getAgeMillis();
final String logNumber = getLogNumber(call);
2,调用logCall方法继续处理,
int callFeatures = getCallFeatures(call.getVideoStateHistory());
logCall(call.getCallerInfo(), logNumber, call.getHandlePresentation(),
toPreciseLogType(call,callLogType), callFeatures, accountHandle,
creationTime, age, null, call.isEmergencyCall());
CallLogManager中有2个logCall方法,只是参数不同而已。
第二个logCall方法主要逻辑如下,
1,根据系统的bool值判断是否保存紧急拨号的信息,
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 = !isEmergency || okToLogEmergencyNumber;
2,将通话的通话类型和时间通过广播发送出去,接收该广播需要相关权限,
sendAddCallBroadcast(callType, duration);
sendAddCallBroadcast方法如下,
Intent callAddIntent = new Intent(ACTION_CALLS_TABLE_ADD_ENTRY);
callAddIntent.putExtra(CALL_TYPE, callType); //类型
callAddIntent.putExtra(CALL_DURATION, duration); //时间
mContext.sendBroadcast(callAddIntent, PERMISSION_PROCESS_CALLLOG_INFO);
3,最后将这些通话信息封装为AddCallArgs对象,调用logCallAsync方法继续处理,
AddCallArgs args = new AddCallArgs(mContext, callerInfo, number, presentation,
callType, features, accountHandle, start, duration, dataUsage);
logCallAsync(args);
AddCallArgs是CallLogManager的一个很简单的内部类, AddCallArgs里面有10个变量,分别保存通话信息。
logCallAsync方法如下,
public AsyncTask<AddCallArgs, Void, Uri[]> logCallAsync(AddCallArgs args) {
return new LogCallAsyncTask().execute(args);
}
LogCallAsyncTask是CallLogManager的内部类,继承AsyncTask并实现了doInBackground方法, LogCallAsyncTask定义如下,
private class LogCallAsyncTask extends AsyncTask<AddCallArgs, Void, Uri[]> {
AsyncTask 原理在此就不论述了。反正一条, execute方法之后,会在子线程中调用doInBackground方法, doInBackground方法逻辑如下,
获取封装通话信息的AddCallArgs对象,然后逐个调用Calls的addCall方法,
int count = callList.length;
Uri[] result = new Uri[count];
for (int i = 0; i < count; i++) {
AddCallArgs c = callList[i];
try {
// 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 */);
•••
CallLog的内部类Calls有3个addCall方法,最后插入数据库的addCall方法将AddCallArgs对象包含的通话信息封装成ContentValues对象,
ContentValues values = new ContentValues(6);
values.put(NUMBER_PRESENTATION, Integer.valueOf(numberPresentation));
values.put(TYPE, Integer.valueOf(callType));
values.put(FEATURES, features);
•••
然后调用addEntryAndRemoveExpiredEntries方法来实现插入数据库的,该方法如下,
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);
通话记录对应的Uri为,
public static final Uri CONTENT_URI = Uri.parse("content://call_log/calls");
小结:
1,更新通话记录时机:在通话完成之后。
2,利用AsyncTask 异步进行插入数据库的过程。
2,通话记录数据库
真实的数据库在系统中的data/data路径下,详细的路径如下,
Contacts2.db这个数据库里面多达30个表单,但是和通话记录相关的只有一个calls表单。
每一次通话就会在calls表单中创建一条通话记录信息,每条信息包含的通话信息项比较多,部分如下,
对应操作数据库的代码为CallLogProvider.java,路径如下,
packages\providers\ContactsProvider\src\com\android\providers\contacts
AndroidManifest.xml中有关CallLogProvider的信息如下,
<provider android:name="CallLogProvider"
android:authorities="call_log"
android:syncable="false" android:multiprocess="false"
android:exported="true"
android:readPermission="android.permission.READ_CALL_LOG"
android:writePermission="android.permission.WRITE_CALL_LOG">
</provider>
操作该数据库的关键字为 call_log ,另外需要读写的权限。