// 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(Callcall, 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.javaprivatevoidlogCall(
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.)finalboolean okToLogEmergencyNumber =
mContext.getResources().getBoolean(R.bool.allow_emergency_numbers_in_call_log);
// Don't log emergency numbers if the device doesn't allow it.finalboolean 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) {
returnnew 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.
*/privateclassLogCallAsyncTaskextendsAsyncTask<AddCallArgs, Void, Uri[]> {@Overrideprotected 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.
*/@Overrideprotected void onPostExecute(Uri[] result) {
for (Uri uri : result) {
if (uri == null) {
Log.w(TAG, "Failed to write call to the log.");
}
}
}
}
// CallLog.java.publicstatic 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;
} elseif (presentation == PhoneConstants.PRESENTATION_PAYPHONE) {
numberPresentation = PRESENTATION_PAYPHONE;
} elseif (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);
finalint currentUserId = userManager.getUserHandle();
finalint 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 privatestatic 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;
}
// CallLogFragment.java // 申请更新数据用于显示privatevoidrefreshData() {
// 是否需要刷新数据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 publicvoidinvalidateCache() {
mContactInfoCache.expireAll();
// Restart the request-processing thread after the next draw.
stopRequestProcessing();
unregisterPreDrawListener();
}
// 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);
}
// CallLogGroupBuilder.javapublicvoid addGroups(Cursor cursor) {
finalintcount = 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.finallong firstDate = cursor.getLong(CallLogQuery.DATE);
finallong 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);
finalint callType = cursor.getInt(CallLogQuery.CALL_TYPE);
final String currentAccountComponentName = cursor.getString(
CallLogQuery.ACCOUNT_COMPONENT_NAME);
final String currentAccountId = cursor.getString(CallLogQuery.ACCOUNT_ID);
finalboolean sameNumber = equalNumbers(firstNumber, currentNumber);
finalboolean sameAccountComponentName = Objects.equals(
firstAccountComponentName,
currentAccountComponentName);
finalboolean sameAccountId = Objects.equals(
firstAccountId,
currentAccountId);
finalboolean sameAccount = sameAccountComponentName && sameAccountId;
finalboolean shouldGroup;
finallong currentCallId = cursor.getLong(CallLogQuery.ID);
finallongdate = cursor.getLong(CallLogQuery.DATE);
if (!sameNumber || !sameAccount) {
// Should only group with calls from the same number.
shouldGroup = false;
} elseif (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);
}
}