From 8f1fbd77f64fdf376e70dd3e7de3c8eb5d9cfbf9 Mon Sep 17 00:00:00 2001 From: linyuh Date: Fri, 15 Jun 2018 13:31:28 -0700 Subject: Improve Coalescer performance Bug: 77813585 Test: CoalescerTest PiperOrigin-RevId: 200764878 Change-Id: I7e3d9c3b4eab1e5de12a108b82c04704550c8c5e --- .../android/dialer/calllog/database/Coalescer.java | 429 ++++++++++----------- .../contract/AnnotatedCallLogContract.java | 85 ++-- .../calllog/datasources/CallLogDataSource.java | 22 -- .../phonelookup/PhoneLookupDataSource.java | 10 - .../systemcalllog/SystemCallLogDataSource.java | 34 -- .../calllog/datasources/util/RowCombiner.java | 108 ------ .../datasources/voicemail/VoicemailDataSource.java | 10 - 7 files changed, 233 insertions(+), 465 deletions(-) delete mode 100644 java/com/android/dialer/calllog/datasources/util/RowCombiner.java (limited to 'java/com/android/dialer/calllog') 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. - * - *

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 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 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 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 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}. - * - *

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. * *

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 { * *

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 { * *

Type: TEXT */ - String FORMATTED_NUMBER = "formatted_number"; + public static final String FORMATTED_NUMBER = "formatted_number"; /** * See {@link android.provider.CallLog.Calls#NUMBER_PRESENTATION}. * *

Type: INTEGER (int) */ - String NUMBER_PRESENTATION = "presentation"; + public static final String NUMBER_PRESENTATION = "presentation"; /** * See {@link android.provider.CallLog.Calls#IS_READ}. * *

TYPE: INTEGER (boolean) */ - String IS_READ = "is_read"; + public static final String IS_READ = "is_read"; /** * See {@link android.provider.CallLog.Calls#NEW}. * *

Type: INTEGER (boolean) */ - String NEW = "new"; + public static final String NEW = "new"; /** * See {@link android.provider.CallLog.Calls#GEOCODED_LOCATION}. * *

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}. * *

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}. * *

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}. * *

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}. * *

Type: INTEGER (int) */ - String CALL_TYPE = "call_type"; - } - - /** - * AnnotatedCallLog table. - * - *

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. - * - *

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. - * - *

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; * *

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. - * - *

{@link #coalesce(List)} may be called from any worker thread at any time. */ public interface CallLogDataSource { @@ -85,22 +79,6 @@ public interface CallLogDataSource { */ ListenableFuture onSuccessfulFill(); - /** - * Combines raw annotated call log rows into a single coalesced row. - * - *

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 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 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 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 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 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 individualRowsSortedByTimestampDesc; - private final ContentValues combinedRow = new ContentValues(); - - public RowCombiner(List 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 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 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 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; @@ -103,14 +101,6 @@ public class VoicemailDataSource implements CallLogDataSource { return Futures.immediateFuture(null); } - @Override - public ContentValues coalesce(List individualRowsSortedByTimestampDesc) { - return new RowCombiner(individualRowsSortedByTimestampDesc) - .useMostRecentInt(AnnotatedCallLog.IS_VOICEMAIL_CALL) - .useMostRecentString(AnnotatedCallLog.VOICEMAIL_CALL_TAG) - .combine(); - } - @Override public void registerContentObservers() {} -- cgit v1.2.3