summaryrefslogtreecommitdiff
path: root/java
diff options
context:
space:
mode:
authorlinyuh <linyuh@google.com>2018-06-15 13:31:28 -0700
committerCopybara-Service <copybara-piper@google.com>2018-06-18 13:20:17 -0700
commit8f1fbd77f64fdf376e70dd3e7de3c8eb5d9cfbf9 (patch)
treee212342f4a8b8a4e29c52c8df34225a1fb11a0b5 /java
parenteed866a9b6bf32cb81e62fc94b94cd3242895c76 (diff)
Improve Coalescer performance
Bug: 77813585 Test: CoalescerTest PiperOrigin-RevId: 200764878 Change-Id: I7e3d9c3b4eab1e5de12a108b82c04704550c8c5e
Diffstat (limited to 'java')
-rw-r--r--java/com/android/dialer/calllog/database/Coalescer.java429
-rw-r--r--java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java85
-rw-r--r--java/com/android/dialer/calllog/datasources/CallLogDataSource.java22
-rw-r--r--java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java10
-rw-r--r--java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java34
-rw-r--r--java/com/android/dialer/calllog/datasources/util/RowCombiner.java108
-rw-r--r--java/com/android/dialer/calllog/datasources/voicemail/VoicemailDataSource.java10
7 files changed, 233 insertions, 465 deletions
diff --git a/java/com/android/dialer/calllog/database/Coalescer.java b/java/com/android/dialer/calllog/database/Coalescer.java
index 903b7e26e..2ad9f9a97 100644
--- a/java/com/android/dialer/calllog/database/Coalescer.java
+++ b/java/com/android/dialer/calllog/database/Coalescer.java
@@ -15,11 +15,9 @@
*/
package com.android.dialer.calllog.database;
-import android.content.ContentValues;
import android.database.Cursor;
import android.provider.CallLog.Calls;
import android.support.annotation.NonNull;
-import android.support.annotation.VisibleForTesting;
import android.support.annotation.WorkerThread;
import android.telecom.PhoneAccountHandle;
import android.text.TextUtils;
@@ -27,9 +25,6 @@ import com.android.dialer.CoalescedIds;
import com.android.dialer.DialerPhoneNumber;
import com.android.dialer.NumberAttributes;
import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
-import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.CoalescedAnnotatedCallLog;
-import com.android.dialer.calllog.datasources.CallLogDataSource;
-import com.android.dialer.calllog.datasources.DataSources;
import com.android.dialer.calllog.model.CoalescedRow;
import com.android.dialer.common.Assert;
import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
@@ -38,35 +33,24 @@ import com.android.dialer.metrics.FutureTimer;
import com.android.dialer.metrics.Metrics;
import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil;
import com.android.dialer.telecom.TelecomUtil;
-import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.protobuf.InvalidProtocolBufferException;
-import java.util.ArrayList;
-import java.util.List;
import java.util.Objects;
import javax.inject.Inject;
-/**
- * Coalesces rows in {@link AnnotatedCallLog} by combining adjacent rows.
- *
- * <p>Applies the logic that determines which adjacent rows should be coalesced, and then delegates
- * to each data source to determine how individual columns should be aggregated.
- */
+/** Combines adjacent rows in {@link AnnotatedCallLog}. */
public class Coalescer {
- private final DataSources dataSources;
private final FutureTimer futureTimer;
private final ListeningExecutorService backgroundExecutorService;
@Inject
Coalescer(
@BackgroundExecutor ListeningExecutorService backgroundExecutorService,
- DataSources dataSources,
FutureTimer futureTimer) {
this.backgroundExecutorService = backgroundExecutorService;
- this.dataSources = dataSources;
this.futureTimer = futureTimer;
}
@@ -108,256 +92,249 @@ public class Coalescer {
return ImmutableList.of();
}
- // Note: This method relies on rowsShouldBeCombined to determine which rows should be combined,
- // but delegates to data sources to actually aggregate column values.
-
- DialerPhoneNumberUtil dialerPhoneNumberUtil = new DialerPhoneNumberUtil();
-
ImmutableList.Builder<CoalescedRow> coalescedRowListBuilder = new ImmutableList.Builder<>();
- int coalescedRowId = 0;
+ RowCombiner rowCombiner = new RowCombiner(allAnnotatedCallLogRowsSortedByTimestampDesc);
+ rowCombiner.startNewGroup();
- // TODO(a bug): Avoid using ContentValues as it doesn't make sense here.
- List<ContentValues> currentRowGroup = new ArrayList<>();
+ long coalescedRowId = 0;
+ do {
+ boolean isRowMerged = rowCombiner.mergeRow(allAnnotatedCallLogRowsSortedByTimestampDesc);
- ContentValues firstRow = cursorRowToContentValues(allAnnotatedCallLogRowsSortedByTimestampDesc);
- currentRowGroup.add(firstRow);
+ if (isRowMerged) {
+ allAnnotatedCallLogRowsSortedByTimestampDesc.moveToNext();
+ }
- while (!currentRowGroup.isEmpty()) {
- // Group consecutive rows
- ContentValues firstRowInGroup = currentRowGroup.get(0);
- ContentValues currentRow = null;
- while (allAnnotatedCallLogRowsSortedByTimestampDesc.moveToNext()) {
- currentRow = cursorRowToContentValues(allAnnotatedCallLogRowsSortedByTimestampDesc);
+ if (!isRowMerged || allAnnotatedCallLogRowsSortedByTimestampDesc.isAfterLast()) {
+ coalescedRowListBuilder.add(
+ rowCombiner.combine().toBuilder().setId(coalescedRowId++).build());
+ rowCombiner.startNewGroup();
+ }
+ } while (!allAnnotatedCallLogRowsSortedByTimestampDesc.isAfterLast());
- if (!rowsShouldBeCombined(dialerPhoneNumberUtil, firstRowInGroup, currentRow)) {
- break;
- }
+ return coalescedRowListBuilder.build();
+ }
- currentRowGroup.add(currentRow);
- }
+ /** Combines rows from {@link AnnotatedCallLog} into a {@link CoalescedRow}. */
+ private static final class RowCombiner {
+ private final CoalescedRow.Builder coalescedRowBuilder = CoalescedRow.newBuilder();
+ private final CoalescedIds.Builder coalescedIdsBuilder = CoalescedIds.newBuilder();
+
+ // Indexes for columns in AnnotatedCallLog
+ private final int idColumn;
+ private final int timestampColumn;
+ private final int numberColumn;
+ private final int formattedNumberColumn;
+ private final int numberPresentationColumn;
+ private final int isReadColumn;
+ private final int isNewColumn;
+ private final int geocodedLocationColumn;
+ private final int phoneAccountComponentNameColumn;
+ private final int phoneAccountIdColumn;
+ private final int featuresColumn;
+ private final int numberAttributesColumn;
+ private final int isVoicemailCallColumn;
+ private final int voicemailCallTagColumn;
+ private final int callTypeColumn;
+
+ // DialerPhoneNumberUtil will be created lazily as its instantiation is expensive.
+ private DialerPhoneNumberUtil dialerPhoneNumberUtil = null;
+
+ RowCombiner(Cursor annotatedCallLogRow) {
+ idColumn = annotatedCallLogRow.getColumnIndexOrThrow(AnnotatedCallLog._ID);
+ timestampColumn = annotatedCallLogRow.getColumnIndexOrThrow(AnnotatedCallLog.TIMESTAMP);
+ numberColumn = annotatedCallLogRow.getColumnIndexOrThrow(AnnotatedCallLog.NUMBER);
+ formattedNumberColumn =
+ annotatedCallLogRow.getColumnIndexOrThrow(AnnotatedCallLog.FORMATTED_NUMBER);
+ numberPresentationColumn =
+ annotatedCallLogRow.getColumnIndexOrThrow(AnnotatedCallLog.NUMBER_PRESENTATION);
+ isReadColumn = annotatedCallLogRow.getColumnIndexOrThrow(AnnotatedCallLog.IS_READ);
+ isNewColumn = annotatedCallLogRow.getColumnIndexOrThrow(AnnotatedCallLog.NEW);
+ geocodedLocationColumn =
+ annotatedCallLogRow.getColumnIndexOrThrow(AnnotatedCallLog.GEOCODED_LOCATION);
+ phoneAccountComponentNameColumn =
+ annotatedCallLogRow.getColumnIndexOrThrow(AnnotatedCallLog.PHONE_ACCOUNT_COMPONENT_NAME);
+ phoneAccountIdColumn =
+ annotatedCallLogRow.getColumnIndexOrThrow(AnnotatedCallLog.PHONE_ACCOUNT_ID);
+ featuresColumn = annotatedCallLogRow.getColumnIndexOrThrow(AnnotatedCallLog.FEATURES);
+ numberAttributesColumn =
+ annotatedCallLogRow.getColumnIndexOrThrow(AnnotatedCallLog.NUMBER_ATTRIBUTES);
+ isVoicemailCallColumn =
+ annotatedCallLogRow.getColumnIndexOrThrow(AnnotatedCallLog.IS_VOICEMAIL_CALL);
+ voicemailCallTagColumn =
+ annotatedCallLogRow.getColumnIndexOrThrow(AnnotatedCallLog.VOICEMAIL_CALL_TAG);
+ callTypeColumn = annotatedCallLogRow.getColumnIndexOrThrow(AnnotatedCallLog.CALL_TYPE);
+ }
- // Coalesce the group into a single row
- ContentValues coalescedRow = coalesceRowsForAllDataSources(currentRowGroup);
- coalescedRow.put(CoalescedAnnotatedCallLog._ID, coalescedRowId++);
- coalescedRow.put(
- CoalescedAnnotatedCallLog.COALESCED_IDS, getCoalescedIds(currentRowGroup).toByteArray());
- coalescedRowListBuilder.add(toCoalescedRowProto(coalescedRow));
+ /**
+ * Prepares {@link RowCombiner} for building a new group of rows by clearing information on all
+ * previously merged rows.
+ */
+ void startNewGroup() {
+ coalescedRowBuilder.clear();
+ coalescedIdsBuilder.clear();
+ }
- // Clear the current group after the rows are coalesced.
- currentRowGroup.clear();
+ /**
+ * Merge the given {@link AnnotatedCallLog} row into the current group.
+ *
+ * @return true if the given row is merged.
+ */
+ boolean mergeRow(Cursor annotatedCallLogRow) {
+ Assert.checkArgument(annotatedCallLogRow.getInt(callTypeColumn) != Calls.VOICEMAIL_TYPE);
- // Add the first of the remaining rows to the current group.
- if (!allAnnotatedCallLogRowsSortedByTimestampDesc.isAfterLast()) {
- currentRowGroup.add(currentRow);
+ if (!canMergeRow(annotatedCallLogRow)) {
+ return false;
}
- }
- return coalescedRowListBuilder.build();
- }
+ // Set fields that don't use the most recent value.
+ //
+ // Currently there is only one such field: "features".
+ // If any call in a group includes a feature (like Wifi/HD), consider the group to have
+ // the feature.
+ coalescedRowBuilder.setFeatures(
+ coalescedRowBuilder.getFeatures() | annotatedCallLogRow.getInt(featuresColumn));
+
+ // Set fields that use the most recent value.
+ // Rows passed to Coalescer are already sorted in descending order of timestamp. If the
+ // coalesced ID list is not empty, it means RowCombiner has merged the most recent row in a
+ // group and there is no need to continue as we only set fields that use the most recent value
+ // from this point forward.
+ if (!coalescedIdsBuilder.getCoalescedIdList().isEmpty()) {
+ coalescedIdsBuilder.addCoalescedId(annotatedCallLogRow.getInt(idColumn));
+ return true;
+ }
- private static ContentValues cursorRowToContentValues(Cursor cursor) {
- ContentValues values = new ContentValues();
- String[] columns = cursor.getColumnNames();
- int length = columns.length;
- for (int i = 0; i < length; i++) {
- if (cursor.getType(i) == Cursor.FIELD_TYPE_BLOB) {
- values.put(columns[i], cursor.getBlob(i));
- } else {
- values.put(columns[i], cursor.getString(i));
+ coalescedRowBuilder
+ .setTimestamp(annotatedCallLogRow.getLong(timestampColumn))
+ .setNumberPresentation(annotatedCallLogRow.getInt(numberPresentationColumn))
+ .setIsRead(annotatedCallLogRow.getInt(isReadColumn) == 1)
+ .setIsNew(annotatedCallLogRow.getInt(isNewColumn) == 1)
+ .setIsVoicemailCall(annotatedCallLogRow.getInt(isVoicemailCallColumn) == 1)
+ .setCallType(annotatedCallLogRow.getInt(callTypeColumn));
+
+ // Two different DialerPhoneNumbers could be combined if they are different but considered
+ // to be a match by libphonenumber; in this case we arbitrarily select the most recent one.
+ try {
+ coalescedRowBuilder.setNumber(
+ DialerPhoneNumber.parseFrom(annotatedCallLogRow.getBlob(numberColumn)));
+ } catch (InvalidProtocolBufferException e) {
+ throw Assert.createAssertionFailException("Unable to parse DialerPhoneNumber bytes", e);
}
- }
- return values;
- }
- /**
- * @param row1 a row from {@link AnnotatedCallLog}
- * @param row2 a row from {@link AnnotatedCallLog}
- */
- private static boolean rowsShouldBeCombined(
- DialerPhoneNumberUtil dialerPhoneNumberUtil, ContentValues row1, ContentValues row2) {
- // Don't combine rows which don't use the same phone account.
- PhoneAccountHandle phoneAccount1 =
- TelecomUtil.composePhoneAccountHandle(
- row1.getAsString(AnnotatedCallLog.PHONE_ACCOUNT_COMPONENT_NAME),
- row1.getAsString(AnnotatedCallLog.PHONE_ACCOUNT_ID));
- PhoneAccountHandle phoneAccount2 =
- TelecomUtil.composePhoneAccountHandle(
- row2.getAsString(AnnotatedCallLog.PHONE_ACCOUNT_COMPONENT_NAME),
- row2.getAsString(AnnotatedCallLog.PHONE_ACCOUNT_ID));
- if (!Objects.equals(phoneAccount1, phoneAccount2)) {
- return false;
- }
+ String formattedNumber = annotatedCallLogRow.getString(formattedNumberColumn);
+ if (!TextUtils.isEmpty(formattedNumber)) {
+ coalescedRowBuilder.setFormattedNumber(formattedNumber);
+ }
- if (!row1.getAsInteger(AnnotatedCallLog.NUMBER_PRESENTATION)
- .equals(row2.getAsInteger(AnnotatedCallLog.NUMBER_PRESENTATION))) {
- return false;
- }
+ String geocodedLocation = annotatedCallLogRow.getString(geocodedLocationColumn);
+ if (!TextUtils.isEmpty(geocodedLocation)) {
+ coalescedRowBuilder.setGeocodedLocation(geocodedLocation);
+ }
- if (!meetsCallFeatureCriteria(row1, row2)) {
- return false;
- }
+ String phoneAccountComponentName =
+ annotatedCallLogRow.getString(phoneAccountComponentNameColumn);
+ if (!TextUtils.isEmpty(phoneAccountComponentName)) {
+ coalescedRowBuilder.setPhoneAccountComponentName(phoneAccountComponentName);
+ }
- DialerPhoneNumber number1;
- DialerPhoneNumber number2;
- try {
- byte[] number1Bytes = row1.getAsByteArray(AnnotatedCallLog.NUMBER);
- byte[] number2Bytes = row2.getAsByteArray(AnnotatedCallLog.NUMBER);
+ String phoneAccountId = annotatedCallLogRow.getString(phoneAccountIdColumn);
+ if (!TextUtils.isEmpty(phoneAccountId)) {
+ coalescedRowBuilder.setPhoneAccountId(phoneAccountId);
+ }
- if (number1Bytes == null || number2Bytes == null) {
- // Empty numbers should not be combined.
- return false;
+ try {
+ coalescedRowBuilder.setNumberAttributes(
+ NumberAttributes.parseFrom(annotatedCallLogRow.getBlob(numberAttributesColumn)));
+ } catch (InvalidProtocolBufferException e) {
+ throw Assert.createAssertionFailException("Unable to parse NumberAttributes bytes", e);
}
- number1 = DialerPhoneNumber.parseFrom(number1Bytes);
- number2 = DialerPhoneNumber.parseFrom(number2Bytes);
- } catch (InvalidProtocolBufferException e) {
- throw Assert.createAssertionFailException("error parsing DialerPhoneNumber proto", e);
- }
- return dialerPhoneNumberUtil.isMatch(number1, number2);
- }
+ String voicemailCallTag = annotatedCallLogRow.getString(voicemailCallTagColumn);
+ if (!TextUtils.isEmpty(voicemailCallTag)) {
+ coalescedRowBuilder.setVoicemailCallTag(voicemailCallTag);
+ }
- /**
- * Returns true if column {@link AnnotatedCallLog#FEATURES} of the two given rows indicate that
- * they can be coalesced.
- */
- private static boolean meetsCallFeatureCriteria(ContentValues row1, ContentValues row2) {
- int row1Features = row1.getAsInteger(AnnotatedCallLog.FEATURES);
- int row2Features = row2.getAsInteger(AnnotatedCallLog.FEATURES);
-
- // A row with FEATURES_ASSISTED_DIALING should not be combined with one without it.
- if ((row1Features & TelephonyManagerCompat.FEATURES_ASSISTED_DIALING)
- != (row2Features & TelephonyManagerCompat.FEATURES_ASSISTED_DIALING)) {
- return false;
+ coalescedIdsBuilder.addCoalescedId(annotatedCallLogRow.getInt(idColumn));
+ return true;
}
- // A video call should not be combined with one that is not a video call.
- if ((row1Features & Calls.FEATURES_VIDEO) != (row2Features & Calls.FEATURES_VIDEO)) {
- return false;
+ /** Builds a {@link CoalescedRow} based on all rows merged into the current group. */
+ CoalescedRow combine() {
+ return coalescedRowBuilder.setCoalescedIds(coalescedIdsBuilder.build()).build();
}
- // A RTT call should not be combined with one that is not a RTT call.
- if ((row1Features & Calls.FEATURES_RTT) != (row2Features & Calls.FEATURES_RTT)) {
- return false;
+ /**
+ * Returns true if the given {@link AnnotatedCallLog} row can be merged into the current group.
+ */
+ private boolean canMergeRow(Cursor annotatedCallLogRow) {
+ return coalescedIdsBuilder.getCoalescedIdList().isEmpty()
+ || (samePhoneAccount(annotatedCallLogRow)
+ && sameNumberPresentation(annotatedCallLogRow)
+ && meetsCallFeatureCriteria(annotatedCallLogRow)
+ && meetsDialerPhoneNumberCriteria(annotatedCallLogRow));
}
- return true;
- }
-
- /**
- * Delegates to data sources to aggregate individual columns to create a new coalesced row.
- *
- * @param individualRows {@link AnnotatedCallLog} rows sorted by timestamp descending
- * @return a {@link CoalescedAnnotatedCallLog} row
- */
- private ContentValues coalesceRowsForAllDataSources(List<ContentValues> individualRows) {
- ContentValues coalescedValues = new ContentValues();
- for (CallLogDataSource dataSource : dataSources.getDataSourcesIncludingSystemCallLog()) {
- coalescedValues.putAll(dataSource.coalesce(individualRows));
+ private boolean samePhoneAccount(Cursor annotatedCallLogRow) {
+ PhoneAccountHandle groupPhoneAccountHandle =
+ TelecomUtil.composePhoneAccountHandle(
+ coalescedRowBuilder.getPhoneAccountComponentName(),
+ coalescedRowBuilder.getPhoneAccountId());
+ PhoneAccountHandle rowPhoneAccountHandle =
+ TelecomUtil.composePhoneAccountHandle(
+ annotatedCallLogRow.getString(phoneAccountComponentNameColumn),
+ annotatedCallLogRow.getString(phoneAccountIdColumn));
+
+ return Objects.equals(groupPhoneAccountHandle, rowPhoneAccountHandle);
}
- return coalescedValues;
- }
- /**
- * Build a {@link CoalescedIds} proto that contains IDs of the rows in {@link AnnotatedCallLog}
- * that are coalesced into one row in {@link CoalescedAnnotatedCallLog}.
- *
- * @param individualRows {@link AnnotatedCallLog} rows sorted by timestamp descending
- * @return A {@link CoalescedIds} proto containing IDs of {@code individualRows}.
- */
- private CoalescedIds getCoalescedIds(List<ContentValues> individualRows) {
- CoalescedIds.Builder coalescedIds = CoalescedIds.newBuilder();
-
- for (ContentValues row : individualRows) {
- coalescedIds.addCoalescedId(Preconditions.checkNotNull(row.getAsLong(AnnotatedCallLog._ID)));
+ private boolean sameNumberPresentation(Cursor annotatedCallLogRow) {
+ return coalescedRowBuilder.getNumberPresentation()
+ == annotatedCallLogRow.getInt(numberPresentationColumn);
}
- return coalescedIds.build();
- }
+ private boolean meetsCallFeatureCriteria(Cursor annotatedCallLogRow) {
+ int groupFeatures = coalescedRowBuilder.getFeatures();
+ int rowFeatures = annotatedCallLogRow.getInt(featuresColumn);
- /**
- * Creates a new {@link CoalescedRow} proto based on the provided {@link ContentValues}.
- *
- * <p>The provided {@link ContentValues} should be one for {@link CoalescedAnnotatedCallLog}.
- */
- @VisibleForTesting
- static CoalescedRow toCoalescedRowProto(ContentValues coalescedContentValues) {
- DialerPhoneNumber number;
- try {
- number =
- DialerPhoneNumber.parseFrom(
- coalescedContentValues.getAsByteArray(CoalescedAnnotatedCallLog.NUMBER));
- } catch (InvalidProtocolBufferException e) {
- throw new IllegalStateException("Couldn't parse DialerPhoneNumber bytes");
- }
+ // A row with FEATURES_ASSISTED_DIALING should not be combined with one without it.
+ if ((groupFeatures & TelephonyManagerCompat.FEATURES_ASSISTED_DIALING)
+ != (rowFeatures & TelephonyManagerCompat.FEATURES_ASSISTED_DIALING)) {
+ return false;
+ }
- CoalescedIds coalescedIds;
- try {
- coalescedIds =
- CoalescedIds.parseFrom(
- coalescedContentValues.getAsByteArray(CoalescedAnnotatedCallLog.COALESCED_IDS));
- } catch (InvalidProtocolBufferException e) {
- throw new IllegalStateException("Couldn't parse CoalescedIds bytes");
- }
+ // A video call should not be combined with one that is not a video call.
+ if ((groupFeatures & Calls.FEATURES_VIDEO) != (rowFeatures & Calls.FEATURES_VIDEO)) {
+ return false;
+ }
- NumberAttributes numberAttributes;
- try {
- numberAttributes =
- NumberAttributes.parseFrom(
- coalescedContentValues.getAsByteArray(CoalescedAnnotatedCallLog.NUMBER_ATTRIBUTES));
- } catch (InvalidProtocolBufferException e) {
- throw new IllegalStateException("Couldn't parse NumberAttributes bytes");
- }
+ // A RTT call should not be combined with one that is not a RTT call.
+ if ((groupFeatures & Calls.FEATURES_RTT) != (rowFeatures & Calls.FEATURES_RTT)) {
+ return false;
+ }
- CoalescedRow.Builder coalescedRowBuilder =
- CoalescedRow.newBuilder()
- .setId(coalescedContentValues.getAsLong(CoalescedAnnotatedCallLog._ID))
- .setTimestamp(coalescedContentValues.getAsLong(CoalescedAnnotatedCallLog.TIMESTAMP))
- .setNumber(number)
- .setNumberPresentation(
- coalescedContentValues.getAsInteger(CoalescedAnnotatedCallLog.NUMBER_PRESENTATION))
- .setIsRead(coalescedContentValues.getAsInteger(CoalescedAnnotatedCallLog.IS_READ) == 1)
- .setIsNew(coalescedContentValues.getAsInteger(CoalescedAnnotatedCallLog.NEW) == 1)
- .setFeatures(coalescedContentValues.getAsInteger(CoalescedAnnotatedCallLog.FEATURES))
- .setCallType(coalescedContentValues.getAsInteger(CoalescedAnnotatedCallLog.CALL_TYPE))
- .setNumberAttributes(numberAttributes)
- .setIsVoicemailCall(
- coalescedContentValues.getAsInteger(CoalescedAnnotatedCallLog.IS_VOICEMAIL_CALL)
- == 1)
- .setCoalescedIds(coalescedIds);
-
- String formattedNumber =
- coalescedContentValues.getAsString(CoalescedAnnotatedCallLog.FORMATTED_NUMBER);
- if (!TextUtils.isEmpty(formattedNumber)) {
- coalescedRowBuilder.setFormattedNumber(formattedNumber);
+ return true;
}
- String geocodedLocation =
- coalescedContentValues.getAsString(CoalescedAnnotatedCallLog.GEOCODED_LOCATION);
- if (!TextUtils.isEmpty(geocodedLocation)) {
- coalescedRowBuilder.setGeocodedLocation(geocodedLocation);
- }
+ private boolean meetsDialerPhoneNumberCriteria(Cursor annotatedCallLogRow) {
+ DialerPhoneNumber groupPhoneNumber = coalescedRowBuilder.getNumber();
- String phoneAccountComponentName =
- coalescedContentValues.getAsString(CoalescedAnnotatedCallLog.PHONE_ACCOUNT_COMPONENT_NAME);
- if (!TextUtils.isEmpty(phoneAccountComponentName)) {
- coalescedRowBuilder.setPhoneAccountComponentName(
- coalescedContentValues.getAsString(
- CoalescedAnnotatedCallLog.PHONE_ACCOUNT_COMPONENT_NAME));
- }
+ DialerPhoneNumber rowPhoneNumber;
+ try {
+ byte[] rowPhoneNumberBytes = annotatedCallLogRow.getBlob(numberColumn);
+ if (rowPhoneNumberBytes == null) {
+ return false; // Empty numbers should not be combined.
+ }
+ rowPhoneNumber = DialerPhoneNumber.parseFrom(rowPhoneNumberBytes);
+ } catch (InvalidProtocolBufferException e) {
+ throw Assert.createAssertionFailException("Unable to parse DialerPhoneNumber bytes", e);
+ }
- String phoneAccountId =
- coalescedContentValues.getAsString(CoalescedAnnotatedCallLog.PHONE_ACCOUNT_ID);
- if (!TextUtils.isEmpty(phoneAccountId)) {
- coalescedRowBuilder.setPhoneAccountId(phoneAccountId);
- }
+ if (dialerPhoneNumberUtil == null) {
+ dialerPhoneNumberUtil = new DialerPhoneNumberUtil();
+ }
- String voicemailCallTag =
- coalescedContentValues.getAsString(CoalescedAnnotatedCallLog.VOICEMAIL_CALL_TAG);
- if (!TextUtils.isEmpty(voicemailCallTag)) {
- coalescedRowBuilder.setVoicemailCallTag(voicemailCallTag);
+ return dialerPhoneNumberUtil.isMatch(groupPhoneNumber, rowPhoneNumber);
}
-
- return coalescedRowBuilder.build();
}
}
diff --git a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java
index 5aa62c2d1..1fdf38ac7 100644
--- a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java
+++ b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java
@@ -28,15 +28,29 @@ public class AnnotatedCallLogContract {
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
- /** Columns shared by {@link AnnotatedCallLog} and {@link CoalescedAnnotatedCallLog}. */
- interface CommonColumns extends BaseColumns {
+ /** AnnotatedCallLog table. */
+ public static final class AnnotatedCallLog implements BaseColumns {
+
+ public static final String TABLE = "AnnotatedCallLog";
+ public static final String DISTINCT_PHONE_NUMBERS = "DistinctPhoneNumbers";
+
+ /** The content URI for this table. */
+ public static final Uri CONTENT_URI =
+ Uri.withAppendedPath(AnnotatedCallLogContract.CONTENT_URI, TABLE);
+
+ /** Content URI for selecting the distinct phone numbers from the AnnotatedCallLog. */
+ public static final Uri DISTINCT_NUMBERS_CONTENT_URI =
+ Uri.withAppendedPath(AnnotatedCallLogContract.CONTENT_URI, DISTINCT_PHONE_NUMBERS);
+
+ /** The MIME type of a {@link android.content.ContentProvider#getType(Uri)} single entry. */
+ public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/annotated_call_log";
/**
* Timestamp of the entry, in milliseconds.
*
* <p>Type: INTEGER (long)
*/
- String TIMESTAMP = "timestamp";
+ public static final String TIMESTAMP = "timestamp";
/**
* The phone number called or number the call came from, encoded as a {@link
@@ -45,7 +59,7 @@ public class AnnotatedCallLogContract {
*
* <p>Type: BLOB
*/
- String NUMBER = "number";
+ public static final String NUMBER = "number";
/**
* The number formatted as it should be displayed to the user. Note that it may not always be
@@ -53,56 +67,56 @@ public class AnnotatedCallLogContract {
*
* <p>Type: TEXT
*/
- String FORMATTED_NUMBER = "formatted_number";
+ public static final String FORMATTED_NUMBER = "formatted_number";
/**
* See {@link android.provider.CallLog.Calls#NUMBER_PRESENTATION}.
*
* <p>Type: INTEGER (int)
*/
- String NUMBER_PRESENTATION = "presentation";
+ public static final String NUMBER_PRESENTATION = "presentation";
/**
* See {@link android.provider.CallLog.Calls#IS_READ}.
*
* <p>TYPE: INTEGER (boolean)
*/
- String IS_READ = "is_read";
+ public static final String IS_READ = "is_read";
/**
* See {@link android.provider.CallLog.Calls#NEW}.
*
* <p>Type: INTEGER (boolean)
*/
- String NEW = "new";
+ public static final String NEW = "new";
/**
* See {@link android.provider.CallLog.Calls#GEOCODED_LOCATION}.
*
* <p>TYPE: TEXT
*/
- String GEOCODED_LOCATION = "geocoded_location";
+ public static final String GEOCODED_LOCATION = "geocoded_location";
/**
* See {@link android.provider.CallLog.Calls#PHONE_ACCOUNT_COMPONENT_NAME}.
*
* <p>TYPE: TEXT
*/
- String PHONE_ACCOUNT_COMPONENT_NAME = "phone_account_component_name";
+ public static final String PHONE_ACCOUNT_COMPONENT_NAME = "phone_account_component_name";
/**
* See {@link android.provider.CallLog.Calls#PHONE_ACCOUNT_ID}.
*
* <p>TYPE: TEXT
*/
- String PHONE_ACCOUNT_ID = "phone_account_id";
+ public static final String PHONE_ACCOUNT_ID = "phone_account_id";
/**
* See {@link android.provider.CallLog.Calls#FEATURES}.
*
* <p>TYPE: INTEGER (int)
*/
- String FEATURES = "features";
+ public static final String FEATURES = "features";
/**
* Additional attributes about the number.
@@ -111,7 +125,7 @@ public class AnnotatedCallLogContract {
*
* @see com.android.dialer.NumberAttributes
*/
- String NUMBER_ATTRIBUTES = "number_attributes";
+ public static final String NUMBER_ATTRIBUTES = "number_attributes";
/**
* Whether the call is to the voicemail inbox.
@@ -121,7 +135,7 @@ public class AnnotatedCallLogContract {
* @see android.telecom.TelecomManager#isVoiceMailNumber(android.telecom.PhoneAccountHandle,
* String)
*/
- String IS_VOICEMAIL_CALL = "is_voicemail_call";
+ public static final String IS_VOICEMAIL_CALL = "is_voicemail_call";
/**
* The "name" of the voicemail inbox. This is provided by the SIM to show as the caller ID
@@ -130,36 +144,14 @@ public class AnnotatedCallLogContract {
*
* @see android.telephony.TelephonyManager#getVoiceMailAlphaTag()
*/
- String VOICEMAIL_CALL_TAG = "voicemail_call_tag";
+ public static final String VOICEMAIL_CALL_TAG = "voicemail_call_tag";
/**
* Copied from {@link android.provider.CallLog.Calls#TYPE}.
*
* <p>Type: INTEGER (int)
*/
- String CALL_TYPE = "call_type";
- }
-
- /**
- * AnnotatedCallLog table.
- *
- * <p>This contains all of the non-coalesced call log entries.
- */
- public static final class AnnotatedCallLog implements CommonColumns {
-
- public static final String TABLE = "AnnotatedCallLog";
- public static final String DISTINCT_PHONE_NUMBERS = "DistinctPhoneNumbers";
-
- /** The content URI for this table. */
- public static final Uri CONTENT_URI =
- Uri.withAppendedPath(AnnotatedCallLogContract.CONTENT_URI, TABLE);
-
- /** Content URI for selecting the distinct phone numbers from the AnnotatedCallLog. */
- public static final Uri DISTINCT_NUMBERS_CONTENT_URI =
- Uri.withAppendedPath(AnnotatedCallLogContract.CONTENT_URI, DISTINCT_PHONE_NUMBERS);
-
- /** The MIME type of a {@link android.content.ContentProvider#getType(Uri)} single entry. */
- public static final String CONTENT_ITEM_TYPE = "vnd.android.cursor.item/annotated_call_log";
+ public static final String CALL_TYPE = "call_type";
/**
* See {@link android.provider.CallLog.Calls#DATA_USAGE}.
@@ -209,21 +201,4 @@ public class AnnotatedCallLogContract {
*/
public static final String CALL_MAPPING_ID = "call_mapping_id";
}
-
- /**
- * Coalesced view of the AnnotatedCallLog table.
- *
- * <p>This is an in-memory view of the {@link AnnotatedCallLog} with some adjacent entries
- * collapsed.
- */
- public static final class CoalescedAnnotatedCallLog implements CommonColumns {
-
- /**
- * IDs of rows in {@link AnnotatedCallLog} that are coalesced into one row in {@link
- * CoalescedAnnotatedCallLog}, encoded as a {@link com.android.dialer.CoalescedIds} proto.
- *
- * <p>Type: BLOB
- */
- public static final String COALESCED_IDS = "coalesced_ids";
- }
}
diff --git a/java/com/android/dialer/calllog/datasources/CallLogDataSource.java b/java/com/android/dialer/calllog/datasources/CallLogDataSource.java
index 75f06d5f6..72676a2ad 100644
--- a/java/com/android/dialer/calllog/datasources/CallLogDataSource.java
+++ b/java/com/android/dialer/calllog/datasources/CallLogDataSource.java
@@ -16,12 +16,8 @@
package com.android.dialer.calllog.datasources;
-import android.content.ContentValues;
import android.support.annotation.MainThread;
-import android.support.annotation.WorkerThread;
-import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract;
import com.google.common.util.concurrent.ListenableFuture;
-import java.util.List;
/**
* A source of data for one or more columns in the annotated call log.
@@ -45,8 +41,6 @@ import java.util.List;
*
* <p>The same data source objects may be reused across multiple checkDirtyAndRebuild cycles, so
* implementors should take care to clear any internal state at the start of a new cycle.
- *
- * <p>{@link #coalesce(List)} may be called from any worker thread at any time.
*/
public interface CallLogDataSource {
@@ -85,22 +79,6 @@ public interface CallLogDataSource {
*/
ListenableFuture<Void> onSuccessfulFill();
- /**
- * Combines raw annotated call log rows into a single coalesced row.
- *
- * <p>May be called by any worker thread at any time so implementations should take care to be
- * threadsafe. (Ideally no state should be required to implement this.)
- *
- * @param individualRowsSortedByTimestampDesc group of fully populated rows from {@link
- * AnnotatedCallLogContract.AnnotatedCallLog} which need to be combined for display purposes.
- * This method should not modify this list.
- * @return a partial {@link AnnotatedCallLogContract.CoalescedAnnotatedCallLog} row containing
- * only columns which this data source is responsible for, which is the result of aggregating
- * {@code individualRowsSortedByTimestampDesc}.
- */
- @WorkerThread
- ContentValues coalesce(List<ContentValues> individualRowsSortedByTimestampDesc);
-
@MainThread
void registerContentObservers();
diff --git a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
index 72e9e0fa9..a80521222 100644
--- a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
+++ b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java
@@ -31,7 +31,6 @@ import com.android.dialer.DialerPhoneNumber;
import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
import com.android.dialer.calllog.datasources.CallLogDataSource;
import com.android.dialer.calllog.datasources.CallLogMutations;
-import com.android.dialer.calllog.datasources.util.RowCombiner;
import com.android.dialer.calllogutils.NumberAttributesBuilder;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
@@ -54,7 +53,6 @@ import com.google.common.util.concurrent.MoreExecutors;
import com.google.protobuf.InvalidProtocolBufferException;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
@@ -290,14 +288,6 @@ public final class PhoneLookupDataSource implements CallLogDataSource {
return null;
}
- @WorkerThread
- @Override
- public ContentValues coalesce(List<ContentValues> individualRowsSortedByTimestampDesc) {
- return new RowCombiner(individualRowsSortedByTimestampDesc)
- .useMostRecentBlob(AnnotatedCallLog.NUMBER_ATTRIBUTES)
- .combine();
- }
-
@MainThread
@Override
public void registerContentObservers() {
diff --git a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
index 1b66f5099..7a12bc4ba 100644
--- a/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
+++ b/java/com/android/dialer/calllog/datasources/systemcalllog/SystemCallLogDataSource.java
@@ -41,7 +41,6 @@ import com.android.dialer.calllog.database.AnnotatedCallLogDatabaseHelper;
import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
import com.android.dialer.calllog.datasources.CallLogDataSource;
import com.android.dialer.calllog.datasources.CallLogMutations;
-import com.android.dialer.calllog.datasources.util.RowCombiner;
import com.android.dialer.calllog.observer.MarkDirtyObserver;
import com.android.dialer.common.Assert;
import com.android.dialer.common.LogUtil;
@@ -231,39 +230,6 @@ public class SystemCallLogDataSource implements CallLogDataSource {
return null;
}
- @Override
- public ContentValues coalesce(List<ContentValues> individualRowsSortedByTimestampDesc) {
- assertNoVoicemailsInRows(individualRowsSortedByTimestampDesc);
-
- return new RowCombiner(individualRowsSortedByTimestampDesc)
- .useMostRecentLong(AnnotatedCallLog.TIMESTAMP)
- .useMostRecentLong(AnnotatedCallLog.NEW)
- .useMostRecentLong(AnnotatedCallLog.IS_READ)
- // Two different DialerPhoneNumbers could be combined if they are different but considered
- // to be an "exact match" by libphonenumber; in this case we arbitrarily select the most
- // recent one.
- .useMostRecentBlob(AnnotatedCallLog.NUMBER)
- .useMostRecentString(AnnotatedCallLog.FORMATTED_NUMBER)
- .useSingleValueInt(AnnotatedCallLog.NUMBER_PRESENTATION)
- .useMostRecentString(AnnotatedCallLog.GEOCODED_LOCATION)
- .useSingleValueString(AnnotatedCallLog.PHONE_ACCOUNT_COMPONENT_NAME)
- .useSingleValueString(AnnotatedCallLog.PHONE_ACCOUNT_ID)
- .useMostRecentLong(AnnotatedCallLog.CALL_TYPE)
- // If any call in a group includes a feature (like Wifi/HD), consider the group to have the
- // feature.
- .bitwiseOr(AnnotatedCallLog.FEATURES)
- .combine();
- }
-
- private void assertNoVoicemailsInRows(List<ContentValues> individualRowsSortedByTimestampDesc) {
- for (ContentValues contentValue : individualRowsSortedByTimestampDesc) {
- if (contentValue.getAsLong(AnnotatedCallLog.CALL_TYPE) != null) {
- Assert.checkArgument(
- contentValue.getAsLong(AnnotatedCallLog.CALL_TYPE) != Calls.VOICEMAIL_TYPE);
- }
- }
- }
-
@TargetApi(Build.VERSION_CODES.N) // Uses try-with-resources
private void handleInsertsAndUpdates(
Context appContext, CallLogMutations mutations, Set<Long> existingAnnotatedCallLogIds) {
diff --git a/java/com/android/dialer/calllog/datasources/util/RowCombiner.java b/java/com/android/dialer/calllog/datasources/util/RowCombiner.java
deleted file mode 100644
index 2bb65cc3e..000000000
--- a/java/com/android/dialer/calllog/datasources/util/RowCombiner.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-package com.android.dialer.calllog.datasources.util;
-
-import android.content.ContentValues;
-import com.android.dialer.common.Assert;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Objects;
-
-/** Convenience class for aggregating row values. */
-public class RowCombiner {
- private final List<ContentValues> individualRowsSortedByTimestampDesc;
- private final ContentValues combinedRow = new ContentValues();
-
- public RowCombiner(List<ContentValues> individualRowsSortedByTimestampDesc) {
- Assert.checkArgument(!individualRowsSortedByTimestampDesc.isEmpty());
- this.individualRowsSortedByTimestampDesc = individualRowsSortedByTimestampDesc;
- }
-
- /** Use the most recent value for the specified column. */
- public RowCombiner useMostRecentInt(String columnName) {
- combinedRow.put(
- columnName, individualRowsSortedByTimestampDesc.get(0).getAsInteger(columnName));
- return this;
- }
-
- /** Use the most recent value for the specified column. */
- public RowCombiner useMostRecentLong(String columnName) {
- combinedRow.put(columnName, individualRowsSortedByTimestampDesc.get(0).getAsLong(columnName));
- return this;
- }
-
- /** Use the most recent value for the specified column. */
- public RowCombiner useMostRecentString(String columnName) {
- combinedRow.put(columnName, individualRowsSortedByTimestampDesc.get(0).getAsString(columnName));
- return this;
- }
-
- public RowCombiner useMostRecentBlob(String columnName) {
- combinedRow.put(
- columnName, individualRowsSortedByTimestampDesc.get(0).getAsByteArray(columnName));
- return this;
- }
-
- /** Asserts that all column values for the given column name are the same, and uses it. */
- public RowCombiner useSingleValueString(String columnName) {
- Iterator<ContentValues> iterator = individualRowsSortedByTimestampDesc.iterator();
- String singleValue = iterator.next().getAsString(columnName);
- while (iterator.hasNext()) {
- String current = iterator.next().getAsString(columnName);
- Assert.checkState(Objects.equals(singleValue, current), "Values different for " + columnName);
- }
- combinedRow.put(columnName, singleValue);
- return this;
- }
-
- /** Asserts that all column values for the given column name are the same, and uses it. */
- public RowCombiner useSingleValueLong(String columnName) {
- Iterator<ContentValues> iterator = individualRowsSortedByTimestampDesc.iterator();
- Long singleValue = iterator.next().getAsLong(columnName);
- while (iterator.hasNext()) {
- Long current = iterator.next().getAsLong(columnName);
- Assert.checkState(Objects.equals(singleValue, current), "Values different for " + columnName);
- }
- combinedRow.put(columnName, singleValue);
- return this;
- }
-
- /** Asserts that all column values for the given column name are the same, and uses it. */
- public RowCombiner useSingleValueInt(String columnName) {
- Iterator<ContentValues> iterator = individualRowsSortedByTimestampDesc.iterator();
- Integer singleValue = iterator.next().getAsInteger(columnName);
- while (iterator.hasNext()) {
- Integer current = iterator.next().getAsInteger(columnName);
- Assert.checkState(Objects.equals(singleValue, current), "Values different for " + columnName);
- }
- combinedRow.put(columnName, singleValue);
- return this;
- }
-
- /** Performs a bitwise OR on the specified column and yields the result. */
- public RowCombiner bitwiseOr(String columnName) {
- int combinedValue = 0;
- for (ContentValues val : individualRowsSortedByTimestampDesc) {
- combinedValue |= val.getAsInteger(columnName);
- }
- combinedRow.put(columnName, combinedValue);
- return this;
- }
-
- public ContentValues combine() {
- return combinedRow;
- }
-}
diff --git a/java/com/android/dialer/calllog/datasources/voicemail/VoicemailDataSource.java b/java/com/android/dialer/calllog/datasources/voicemail/VoicemailDataSource.java
index 7a230220e..cbda9ac81 100644
--- a/java/com/android/dialer/calllog/datasources/voicemail/VoicemailDataSource.java
+++ b/java/com/android/dialer/calllog/datasources/voicemail/VoicemailDataSource.java
@@ -25,7 +25,6 @@ import com.android.dialer.DialerPhoneNumber;
import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
import com.android.dialer.calllog.datasources.CallLogDataSource;
import com.android.dialer.calllog.datasources.CallLogMutations;
-import com.android.dialer.calllog.datasources.util.RowCombiner;
import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor;
import com.android.dialer.compat.telephony.TelephonyManagerCompat;
import com.android.dialer.inject.ApplicationContext;
@@ -35,7 +34,6 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.protobuf.InvalidProtocolBufferException;
-import java.util.List;
import java.util.Map.Entry;
import javax.inject.Inject;
@@ -104,14 +102,6 @@ public class VoicemailDataSource implements CallLogDataSource {
}
@Override
- public ContentValues coalesce(List<ContentValues> individualRowsSortedByTimestampDesc) {
- return new RowCombiner(individualRowsSortedByTimestampDesc)
- .useMostRecentInt(AnnotatedCallLog.IS_VOICEMAIL_CALL)
- .useMostRecentString(AnnotatedCallLog.VOICEMAIL_CALL_TAG)
- .combine();
- }
-
- @Override
public void registerContentObservers() {}
@Override