From 34daf89e6a8db5b329424db85a79362536ee8245 Mon Sep 17 00:00:00 2001 From: linyuh Date: Fri, 11 May 2018 15:58:37 -0700 Subject: Move coalescing logic out of AnnotatedCallLogContentProvider. Bug: 79232964 Test: CoalescerTest, AnnotatedCallLogCursorLoaderTest, and manual testing. PiperOrigin-RevId: 196321995 Change-Id: I016bf28e0c09cf4fee5bc5a9115335fb35b7f7e9 --- .../database/AnnotatedCallLogContentProvider.java | 50 +------ .../android/dialer/calllog/database/Coalescer.java | 151 +++++++++++++++++++-- .../contract/AnnotatedCallLogContract.java | 12 +- .../calllog/ui/AnnotatedCallLogCursorLoader.java | 36 +++++ .../ui/CoalescedAnnotatedCallLogCursorLoader.java | 132 ------------------ .../dialer/calllog/ui/NewCallLogAdapter.java | 3 +- .../dialer/calllog/ui/NewCallLogFragment.java | 57 ++++++-- .../dialer/calllog/ui/NewCallLogViewHolder.java | 8 +- 8 files changed, 232 insertions(+), 217 deletions(-) create mode 100644 java/com/android/dialer/calllog/ui/AnnotatedCallLogCursorLoader.java delete mode 100644 java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java (limited to 'java') diff --git a/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java b/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java index 7fc474a98..3ca76ee23 100644 --- a/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java +++ b/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java @@ -29,16 +29,12 @@ import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteQueryBuilder; import android.net.Uri; import android.os.Build; -import android.provider.CallLog.Calls; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract; import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog; -import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.CoalescedAnnotatedCallLog; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; -import com.android.dialer.metrics.Metrics; -import com.android.dialer.metrics.MetricsComponent; import java.util.ArrayList; import java.util.Arrays; @@ -50,7 +46,6 @@ public class AnnotatedCallLogContentProvider extends ContentProvider { private static final int ANNOTATED_CALL_LOG_TABLE_CODE = 1; private static final int ANNOTATED_CALL_LOG_TABLE_ID_CODE = 2; private static final int ANNOTATED_CALL_LOG_TABLE_DISTINCT_NUMBER_CODE = 3; - private static final int COALESCED_ANNOTATED_CALL_LOG_TABLE_CODE = 4; private static final UriMatcher uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); @@ -65,10 +60,6 @@ public class AnnotatedCallLogContentProvider extends ContentProvider { AnnotatedCallLogContract.AUTHORITY, AnnotatedCallLog.DISTINCT_PHONE_NUMBERS, ANNOTATED_CALL_LOG_TABLE_DISTINCT_NUMBER_CODE); - uriMatcher.addURI( - AnnotatedCallLogContract.AUTHORITY, - CoalescedAnnotatedCallLog.TABLE, - COALESCED_ANNOTATED_CALL_LOG_TABLE_CODE); } private AnnotatedCallLogDatabaseHelper databaseHelper; @@ -142,33 +133,6 @@ public class AnnotatedCallLogContentProvider extends ContentProvider { LogUtil.w("AnnotatedCallLogContentProvider.query", "cursor was null"); } return cursor; - case COALESCED_ANNOTATED_CALL_LOG_TABLE_CODE: - Assert.checkArgument( - projection == CoalescedAnnotatedCallLog.ALL_COLUMNS, - "only ALL_COLUMNS projection supported for coalesced call log"); - Assert.checkArgument(selection == null, "selection not supported for coalesced call log"); - Assert.checkArgument( - selectionArgs == null, "selection args not supported for coalesced call log"); - Assert.checkArgument(sortOrder == null, "sort order not supported for coalesced call log"); - MetricsComponent.get(getContext()).metrics().startTimer(Metrics.NEW_CALL_LOG_COALESCE); - try (Cursor allAnnotatedCallLogRows = - queryBuilder.query( - db, - null, - String.format("%s != ?", CoalescedAnnotatedCallLog.CALL_TYPE), - new String[] {Integer.toString(Calls.VOICEMAIL_TYPE)}, - null, - null, - AnnotatedCallLog.TIMESTAMP + " DESC")) { - Cursor coalescedRows = - CallLogDatabaseComponent.get(getContext()) - .coalescer() - .coalesce(allAnnotatedCallLogRows); - coalescedRows.setNotificationUri( - getContext().getContentResolver(), CoalescedAnnotatedCallLog.CONTENT_URI); - MetricsComponent.get(getContext()).metrics().stopTimer(Metrics.NEW_CALL_LOG_COALESCE); - return coalescedRows; - } default: throw new IllegalArgumentException("Unknown uri: " + uri); } @@ -207,8 +171,6 @@ public class AnnotatedCallLogContentProvider extends ContentProvider { break; case ANNOTATED_CALL_LOG_TABLE_DISTINCT_NUMBER_CODE: throw new UnsupportedOperationException(); - case COALESCED_ANNOTATED_CALL_LOG_TABLE_CODE: - throw new UnsupportedOperationException("coalesced call log does not support inserting"); default: throw new IllegalArgumentException("Unknown uri: " + uri); } @@ -245,8 +207,6 @@ public class AnnotatedCallLogContentProvider extends ContentProvider { break; case ANNOTATED_CALL_LOG_TABLE_DISTINCT_NUMBER_CODE: throw new UnsupportedOperationException(); - case COALESCED_ANNOTATED_CALL_LOG_TABLE_CODE: - throw new UnsupportedOperationException("coalesced call log does not support deleting"); default: throw new IllegalArgumentException("Unknown uri: " + uri); } @@ -300,7 +260,6 @@ public class AnnotatedCallLogContentProvider extends ContentProvider { } return rows; case ANNOTATED_CALL_LOG_TABLE_DISTINCT_NUMBER_CODE: - case COALESCED_ANNOTATED_CALL_LOG_TABLE_CODE: throw new UnsupportedOperationException(); default: throw new IllegalArgumentException("Unknown uri: " + uri); @@ -336,9 +295,6 @@ public class AnnotatedCallLogContentProvider extends ContentProvider { break; case ANNOTATED_CALL_LOG_TABLE_DISTINCT_NUMBER_CODE: throw new UnsupportedOperationException(); - case COALESCED_ANNOTATED_CALL_LOG_TABLE_CODE: - throw new UnsupportedOperationException( - "coalesced call log does not support applyBatch"); default: throw new IllegalArgumentException("Unknown uri: " + operation.getUri()); } @@ -380,10 +336,6 @@ public class AnnotatedCallLogContentProvider extends ContentProvider { } private void notifyChange(Uri uri) { - getContext().getContentResolver().notifyChange(uri, null); - // Any time the annotated call log changes, we need to also notify observers of the - // CoalescedAnnotatedCallLog, since that is just a massaged in-memory view of the real annotated - // call log table. - getContext().getContentResolver().notifyChange(CoalescedAnnotatedCallLog.CONTENT_URI, null); + getContext().getContentResolver().notifyChange(uri, /* observer = */ null); } } diff --git a/java/com/android/dialer/calllog/database/Coalescer.java b/java/com/android/dialer/calllog/database/Coalescer.java index 8a16be2da..a889b9fe6 100644 --- a/java/com/android/dialer/calllog/database/Coalescer.java +++ b/java/com/android/dialer/calllog/database/Coalescer.java @@ -22,17 +22,25 @@ import android.provider.CallLog.Calls; import android.support.annotation.NonNull; import android.support.annotation.WorkerThread; import android.telecom.PhoneAccountHandle; +import android.text.TextUtils; 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; import com.android.dialer.compat.telephony.TelephonyManagerCompat; +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.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; import com.google.protobuf.InvalidProtocolBufferException; import java.util.ArrayList; import java.util.List; @@ -41,32 +49,76 @@ import java.util.Objects; import javax.inject.Inject; /** - * Coalesces call log rows by combining some adjacent rows. + * 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. */ public class Coalescer { + + // Indexes for CoalescedAnnotatedCallLog.ALL_COLUMNS + private static final int ID = 0; + private static final int TIMESTAMP = 1; + private static final int NUMBER = 2; + private static final int FORMATTED_NUMBER = 3; + private static final int NUMBER_PRESENTATION = 4; + private static final int IS_READ = 5; + private static final int NEW = 6; + private static final int GEOCODED_LOCATION = 7; + private static final int PHONE_ACCOUNT_COMPONENT_NAME = 8; + private static final int PHONE_ACCOUNT_ID = 9; + private static final int FEATURES = 10; + private static final int NUMBER_ATTRIBUTES = 11; + private static final int IS_VOICEMAIL_CALL = 12; + private static final int VOICEMAIL_CALL_TAG = 13; + private static final int CALL_TYPE = 14; + private static final int COALESCED_IDS = 15; + private final DataSources dataSources; + private final FutureTimer futureTimer; + private final ListeningExecutorService backgroundExecutorService; @Inject - Coalescer(DataSources dataSources) { + Coalescer( + @BackgroundExecutor ListeningExecutorService backgroundExecutorService, + DataSources dataSources, + FutureTimer futureTimer) { + this.backgroundExecutorService = backgroundExecutorService; this.dataSources = dataSources; + this.futureTimer = futureTimer; + } + + /** + * Given rows from {@link AnnotatedCallLog}, combine adjacent ones which should be collapsed for + * display purposes. + * + * @param allAnnotatedCallLogRowsSortedByTimestampDesc {@link AnnotatedCallLog} rows sorted in + * descending order of timestamp. + * @return a future of a {@link MatrixCursor} containing the {@link CoalescedAnnotatedCallLog} + * rows to display + */ + public ListenableFuture coalesce( + @NonNull Cursor allAnnotatedCallLogRowsSortedByTimestampDesc) { + ListenableFuture coalescingFuture = + backgroundExecutorService.submit( + () -> coalesceInternal(Assert.isNotNull(allAnnotatedCallLogRowsSortedByTimestampDesc))); + futureTimer.applyTiming(coalescingFuture, Metrics.NEW_CALL_LOG_COALESCE); + return coalescingFuture; } /** - * Reads the entire {@link AnnotatedCallLog} database into memory from the provided {@code - * allAnnotatedCallLog} parameter and then builds and returns a new {@link MatrixCursor} which is - * the result of combining adjacent rows which should be collapsed for display purposes. + * Reads the entire {@link AnnotatedCallLog} into memory from the provided cursor and then builds + * and returns a new {@link MatrixCursor} of {@link CoalescedAnnotatedCallLog}, which is the + * result of combining adjacent rows which should be collapsed for display purposes. * - * @param allAnnotatedCallLogRowsSortedByTimestampDesc all {@link AnnotatedCallLog} rows, sorted - * by timestamp descending + * @param allAnnotatedCallLogRowsSortedByTimestampDesc {@link AnnotatedCallLog} rows sorted in + * descending order of timestamp. * @return a new {@link MatrixCursor} containing the {@link CoalescedAnnotatedCallLog} rows to * display */ @WorkerThread @NonNull - Cursor coalesce(@NonNull Cursor allAnnotatedCallLogRowsSortedByTimestampDesc) { + private Cursor coalesceInternal(Cursor allAnnotatedCallLogRowsSortedByTimestampDesc) { Assert.isWorkerThread(); // Note: This method relies on rowsShouldBeCombined to determine which rows should be combined, @@ -77,7 +129,7 @@ public class Coalescer { MatrixCursor allCoalescedRowsMatrixCursor = new MatrixCursor( CoalescedAnnotatedCallLog.ALL_COLUMNS, - Assert.isNotNull(allAnnotatedCallLogRowsSortedByTimestampDesc).getCount()); + allAnnotatedCallLogRowsSortedByTimestampDesc.getCount()); if (!allAnnotatedCallLogRowsSortedByTimestampDesc.moveToFirst()) { return allCoalescedRowsMatrixCursor; @@ -252,4 +304,85 @@ public class Coalescer { rowBuilder.add(entry.getKey(), entry.getValue()); } } + + /** + * Creates a new {@link CoalescedRow} based on the data at the provided cursor's current position. + * + *

The provided cursor should be one for {@link CoalescedAnnotatedCallLog}. + */ + public static CoalescedRow toRow(Cursor coalescedAnnotatedCallLogCursor) { + DialerPhoneNumber number; + try { + number = DialerPhoneNumber.parseFrom(coalescedAnnotatedCallLogCursor.getBlob(NUMBER)); + } catch (InvalidProtocolBufferException e) { + throw new IllegalStateException("Couldn't parse DialerPhoneNumber bytes"); + } + + CoalescedIds coalescedIds; + try { + coalescedIds = CoalescedIds.parseFrom(coalescedAnnotatedCallLogCursor.getBlob(COALESCED_IDS)); + } catch (InvalidProtocolBufferException e) { + throw new IllegalStateException("Couldn't parse CoalescedIds bytes"); + } + + NumberAttributes numberAttributes; + try { + numberAttributes = + NumberAttributes.parseFrom(coalescedAnnotatedCallLogCursor.getBlob(NUMBER_ATTRIBUTES)); + } catch (InvalidProtocolBufferException e) { + throw new IllegalStateException("Couldn't parse NumberAttributes bytes"); + } + + CoalescedRow.Builder coalescedRowBuilder = + CoalescedRow.newBuilder() + .setId(coalescedAnnotatedCallLogCursor.getLong(ID)) + .setTimestamp(coalescedAnnotatedCallLogCursor.getLong(TIMESTAMP)) + .setNumber(number) + .setNumberPresentation(coalescedAnnotatedCallLogCursor.getInt(NUMBER_PRESENTATION)) + .setIsRead(coalescedAnnotatedCallLogCursor.getInt(IS_READ) == 1) + .setIsNew(coalescedAnnotatedCallLogCursor.getInt(NEW) == 1) + .setFeatures(coalescedAnnotatedCallLogCursor.getInt(FEATURES)) + .setCallType(coalescedAnnotatedCallLogCursor.getInt(CALL_TYPE)) + .setNumberAttributes(numberAttributes) + .setIsVoicemailCall(coalescedAnnotatedCallLogCursor.getInt(IS_VOICEMAIL_CALL) == 1) + .setCoalescedIds(coalescedIds); + + String formattedNumber = coalescedAnnotatedCallLogCursor.getString(FORMATTED_NUMBER); + if (!TextUtils.isEmpty(formattedNumber)) { + coalescedRowBuilder.setFormattedNumber(formattedNumber); + } + + String geocodedLocation = coalescedAnnotatedCallLogCursor.getString(GEOCODED_LOCATION); + if (!TextUtils.isEmpty(geocodedLocation)) { + coalescedRowBuilder.setGeocodedLocation(geocodedLocation); + } + + String phoneAccountComponentName = + coalescedAnnotatedCallLogCursor.getString(PHONE_ACCOUNT_COMPONENT_NAME); + if (!TextUtils.isEmpty(phoneAccountComponentName)) { + coalescedRowBuilder.setPhoneAccountComponentName( + coalescedAnnotatedCallLogCursor.getString(PHONE_ACCOUNT_COMPONENT_NAME)); + } + + String phoneAccountId = coalescedAnnotatedCallLogCursor.getString(PHONE_ACCOUNT_ID); + if (!TextUtils.isEmpty(phoneAccountId)) { + coalescedRowBuilder.setPhoneAccountId(phoneAccountId); + } + + String voicemailCallTag = coalescedAnnotatedCallLogCursor.getString(VOICEMAIL_CALL_TAG); + if (!TextUtils.isEmpty(voicemailCallTag)) { + coalescedRowBuilder.setVoicemailCallTag(voicemailCallTag); + } + + return coalescedRowBuilder.build(); + } + + /** + * Returns the timestamp at the provided cursor's current position. + * + *

The provided cursor should be one for {@link CoalescedAnnotatedCallLog}. + */ + public static long getTimestamp(Cursor coalescedAnnotatedCallLogCursor) { + return coalescedAnnotatedCallLogCursor.getLong(TIMESTAMP); + } } diff --git a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java index ee888d196..8ca151c64 100644 --- a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java +++ b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java @@ -225,7 +225,7 @@ public class AnnotatedCallLogContract { /** * An unique id to associate this call log row to a {@link android.telecom.Call}. * - *

For pre-Q device, this is same as {@link TIMESTAMP}. + *

For pre-Q device, this is same as {@link #TIMESTAMP}. * *

For Q+ device, this will be copied from {@link android.provider.CallLog.Calls}. * @@ -244,16 +244,6 @@ public class AnnotatedCallLogContract { */ public static final class CoalescedAnnotatedCallLog implements CommonColumns { - public static final String TABLE = "CoalescedAnnotatedCallLog"; - - /** The content URI for this table. */ - public static final Uri CONTENT_URI = - Uri.withAppendedPath(AnnotatedCallLogContract.CONTENT_URI, TABLE); - - /** The MIME type of a {@link android.content.ContentProvider#getType(Uri)} single entry. */ - public static final String CONTENT_ITEM_TYPE = - "vnd.android.cursor.item/coalesced_annotated_call_log"; - /** * IDs of rows in {@link AnnotatedCallLog} that are coalesced into one row in {@link * CoalescedAnnotatedCallLog}, encoded as a {@link com.android.dialer.CoalescedIds} proto. diff --git a/java/com/android/dialer/calllog/ui/AnnotatedCallLogCursorLoader.java b/java/com/android/dialer/calllog/ui/AnnotatedCallLogCursorLoader.java new file mode 100644 index 000000000..6111cd8bf --- /dev/null +++ b/java/com/android/dialer/calllog/ui/AnnotatedCallLogCursorLoader.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2018 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.ui; + +import android.content.Context; +import android.provider.CallLog.Calls; +import android.support.v4.content.CursorLoader; +import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog; + +/** Cursor loader for {@link AnnotatedCallLog}. */ +final class AnnotatedCallLogCursorLoader extends CursorLoader { + + AnnotatedCallLogCursorLoader(Context context) { + super( + context, + AnnotatedCallLog.CONTENT_URI, + /* projection = */ null, + /* selection = */ AnnotatedCallLog.CALL_TYPE + " != ?", + /* selectionArgs = */ new String[] {Integer.toString(Calls.VOICEMAIL_TYPE)}, + /* sortOrder = */ AnnotatedCallLog.TIMESTAMP + " DESC"); + } +} diff --git a/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java b/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java deleted file mode 100644 index 164bb7dad..000000000 --- a/java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java +++ /dev/null @@ -1,132 +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.ui; - -import android.content.Context; -import android.database.Cursor; -import android.support.v4.content.CursorLoader; -import android.text.TextUtils; -import com.android.dialer.CoalescedIds; -import com.android.dialer.DialerPhoneNumber; -import com.android.dialer.NumberAttributes; -import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.CoalescedAnnotatedCallLog; -import com.android.dialer.calllog.model.CoalescedRow; -import com.google.protobuf.InvalidProtocolBufferException; - -/** CursorLoader for the coalesced annotated call log. */ -final class CoalescedAnnotatedCallLogCursorLoader extends CursorLoader { - - // Indexes for CoalescedAnnotatedCallLog.ALL_COLUMNS - private static final int ID = 0; - private static final int TIMESTAMP = 1; - private static final int NUMBER = 2; - private static final int FORMATTED_NUMBER = 3; - private static final int NUMBER_PRESENTATION = 4; - private static final int IS_READ = 5; - private static final int NEW = 6; - private static final int GEOCODED_LOCATION = 7; - private static final int PHONE_ACCOUNT_COMPONENT_NAME = 8; - private static final int PHONE_ACCOUNT_ID = 9; - private static final int FEATURES = 10; - private static final int NUMBER_ATTRIBUTES = 11; - private static final int IS_VOICEMAIL_CALL = 12; - private static final int VOICEMAIL_CALL_TAG = 13; - private static final int CALL_TYPE = 14; - private static final int COALESCED_IDS = 15; - - CoalescedAnnotatedCallLogCursorLoader(Context context) { - // CoalescedAnnotatedCallLog requires that PROJECTION be ALL_COLUMNS and the following params be - // null. - super( - context, - CoalescedAnnotatedCallLog.CONTENT_URI, - CoalescedAnnotatedCallLog.ALL_COLUMNS, - null, - null, - null); - } - - /** Creates a new {@link CoalescedRow} from the provided cursor using the current position. */ - static CoalescedRow toRow(Cursor cursor) { - DialerPhoneNumber number; - try { - number = DialerPhoneNumber.parseFrom(cursor.getBlob(NUMBER)); - } catch (InvalidProtocolBufferException e) { - throw new IllegalStateException("Couldn't parse DialerPhoneNumber bytes"); - } - - CoalescedIds coalescedIds; - try { - coalescedIds = CoalescedIds.parseFrom(cursor.getBlob(COALESCED_IDS)); - } catch (InvalidProtocolBufferException e) { - throw new IllegalStateException("Couldn't parse CoalescedIds bytes"); - } - - NumberAttributes numberAttributes; - try { - numberAttributes = NumberAttributes.parseFrom(cursor.getBlob(NUMBER_ATTRIBUTES)); - } catch (InvalidProtocolBufferException e) { - throw new IllegalStateException("Couldn't parse NumberAttributes bytes"); - } - - CoalescedRow.Builder coalescedRowBuilder = - CoalescedRow.newBuilder() - .setId(cursor.getLong(ID)) - .setTimestamp(cursor.getLong(TIMESTAMP)) - .setNumber(number) - .setNumberPresentation(cursor.getInt(NUMBER_PRESENTATION)) - .setIsRead(cursor.getInt(IS_READ) == 1) - .setIsNew(cursor.getInt(NEW) == 1) - .setFeatures(cursor.getInt(FEATURES)) - .setCallType(cursor.getInt(CALL_TYPE)) - .setNumberAttributes(numberAttributes) - .setIsVoicemailCall(cursor.getInt(IS_VOICEMAIL_CALL) == 1) - .setCoalescedIds(coalescedIds); - - String formattedNumber = cursor.getString(FORMATTED_NUMBER); - if (!TextUtils.isEmpty(formattedNumber)) { - coalescedRowBuilder.setFormattedNumber(formattedNumber); - } - - String geocodedLocation = cursor.getString(GEOCODED_LOCATION); - if (!TextUtils.isEmpty(geocodedLocation)) { - coalescedRowBuilder.setGeocodedLocation(geocodedLocation); - } - - String phoneAccountComponentName = cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME); - if (!TextUtils.isEmpty(phoneAccountComponentName)) { - coalescedRowBuilder.setPhoneAccountComponentName( - cursor.getString(PHONE_ACCOUNT_COMPONENT_NAME)); - } - - String phoneAccountId = cursor.getString(PHONE_ACCOUNT_ID); - if (!TextUtils.isEmpty(phoneAccountId)) { - coalescedRowBuilder.setPhoneAccountId(phoneAccountId); - } - - String voicemailCallTag = cursor.getString(VOICEMAIL_CALL_TAG); - if (!TextUtils.isEmpty(voicemailCallTag)) { - coalescedRowBuilder.setVoicemailCallTag(voicemailCallTag); - } - - return coalescedRowBuilder.build(); - } - - static long getTimestamp(Cursor cursor) { - return cursor.getLong(TIMESTAMP); - } -} diff --git a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java index 69cc02be4..501cf1657 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogAdapter.java @@ -26,6 +26,7 @@ import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerView.ViewHolder; import android.view.LayoutInflater; import android.view.ViewGroup; +import com.android.dialer.calllog.database.Coalescer; import com.android.dialer.calllogutils.CallLogDates; import com.android.dialer.common.Assert; import com.android.dialer.duo.Duo; @@ -147,7 +148,7 @@ final class NewCallLogAdapter extends RecyclerView.Adapter { int numItemsInToday = 0; int numItemsInYesterday = 0; do { - long timestamp = CoalescedAnnotatedCallLogCursorLoader.getTimestamp(cursor); + long timestamp = Coalescer.getTimestamp(cursor); long dayDifference = CallLogDates.getDayDifference(currentTimeMillis, timestamp); if (dayDifference == 0) { numItemsInToday++; diff --git a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java index bc5750770..1890b7433 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogFragment.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogFragment.java @@ -31,14 +31,18 @@ import android.view.View; import android.view.ViewGroup; import com.android.dialer.calllog.CallLogComponent; import com.android.dialer.calllog.RefreshAnnotatedCallLogReceiver; +import com.android.dialer.calllog.database.CallLogDatabaseComponent; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.DefaultFutureCallback; +import com.android.dialer.common.concurrent.DialerExecutorComponent; +import com.android.dialer.common.concurrent.SupportUiListener; import com.android.dialer.common.concurrent.ThreadUtil; import com.android.dialer.metrics.Metrics; import com.android.dialer.metrics.MetricsComponent; import com.android.dialer.metrics.jank.RecyclerViewJankLogger; import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import java.util.concurrent.TimeUnit; @@ -48,8 +52,9 @@ public final class NewCallLogFragment extends Fragment implements LoaderCallback @VisibleForTesting static final long MARK_ALL_CALLS_READ_WAIT_MILLIS = TimeUnit.SECONDS.toMillis(3); - private RefreshAnnotatedCallLogReceiver refreshAnnotatedCallLogReceiver; private RecyclerView recyclerView; + private RefreshAnnotatedCallLogReceiver refreshAnnotatedCallLogReceiver; + private SupportUiListener coalesingAnnotatedCallLogListener; private boolean shouldMarkCallsRead = false; private final Runnable setShouldMarkCallsReadTrue = () -> shouldMarkCallsRead = true; @@ -188,6 +193,11 @@ public final class NewCallLogFragment extends Fragment implements LoaderCallback new RecyclerViewJankLogger( MetricsComponent.get(getContext()).metrics(), Metrics.NEW_CALL_LOG_JANK_EVENT_NAME)); + coalesingAnnotatedCallLogListener = + DialerExecutorComponent.get(getContext()) + .createUiListener( + getChildFragmentManager(), + /* taskId = */ "NewCallLogFragment.coalescingAnnotatedCallLog"); getLoaderManager().restartLoader(0, null, this); return view; @@ -214,7 +224,7 @@ public final class NewCallLogFragment extends Fragment implements LoaderCallback @Override public Loader onCreateLoader(int id, Bundle args) { LogUtil.enterBlock("NewCallLogFragment.onCreateLoader"); - return new CoalescedAnnotatedCallLogCursorLoader(getContext()); + return new AnnotatedCallLogCursorLoader(Assert.isNotNull(getContext())); } @Override @@ -228,17 +238,38 @@ public final class NewCallLogFragment extends Fragment implements LoaderCallback return; } - // TODO(zachh): Handle empty cursor by showing empty view. - if (recyclerView.getAdapter() == null) { - recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); - // Note: It's not clear if this callback can be invoked when there's no associated activity, - // but if crashes are observed here it may be possible to use getContext() instead. - Activity activity = Assert.isNotNull(getActivity()); - recyclerView.setAdapter( - new NewCallLogAdapter(activity, newCursor, System::currentTimeMillis)); - } else { - ((NewCallLogAdapter) recyclerView.getAdapter()).updateCursor(newCursor); - } + // Start combining adjacent rows which should be collapsed for display purposes. + // This is a time-consuming process so we will do it in the background. + ListenableFuture coalescedCursorFuture = + CallLogDatabaseComponent.get(getContext()).coalescer().coalesce(newCursor); + + coalesingAnnotatedCallLogListener.listen( + getContext(), + coalescedCursorFuture, + coalescedCursor -> { + LogUtil.i("NewCallLogFragment.onLoadFinished", "coalescing succeeded"); + + // TODO(zachh): Handle empty cursor by showing empty view. + if (recyclerView.getAdapter() == null) { + recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); + // Note: It's not clear if this callback can be invoked when there's no associated + // activity, but if crashes are observed here it may be possible to use getContext() + // instead. + Activity activity = Assert.isNotNull(getActivity()); + recyclerView.setAdapter( + new NewCallLogAdapter(activity, coalescedCursor, System::currentTimeMillis)); + } else { + ((NewCallLogAdapter) recyclerView.getAdapter()).updateCursor(coalescedCursor); + } + }, + throwable -> { + // Coalescing can fail if the cursor passed to Coalescer is closed by the loader while + // the work is still in progress. + // This can happen when the loader restarts and finishes loading data before the + // coalescing work is completed. + // TODO(linyuh): throw an exception here if the failure above can be avoided. + LogUtil.e("NewCallLogFragment.onLoadFinished", "coalescing failed", throwable); + }); } @Override diff --git a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java index 5f3cd96c4..fccd8b9c4 100644 --- a/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java +++ b/java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java @@ -29,6 +29,7 @@ import android.text.TextUtils; import android.view.View; import android.widget.ImageView; import android.widget.TextView; +import com.android.dialer.calllog.database.Coalescer; import com.android.dialer.calllog.model.CoalescedRow; import com.android.dialer.calllog.ui.NewCallLogAdapter.PopCounts; import com.android.dialer.calllog.ui.menu.NewCallLogMenu; @@ -96,9 +97,12 @@ final class NewCallLogViewHolder extends RecyclerView.ViewHolder { uiExecutorService = DialerExecutorComponent.get(activity).uiExecutor(); } - /** @param cursor a cursor from {@link CoalescedAnnotatedCallLogCursorLoader}. */ + /** + * @param cursor a cursor for {@link + * com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.CoalescedAnnotatedCallLog}. + */ void bind(Cursor cursor) { - CoalescedRow row = CoalescedAnnotatedCallLogCursorLoader.toRow(cursor); + CoalescedRow row = Coalescer.toRow(cursor); currentRowId = row.getId(); // Used to make sure async updates are applied to the correct views // Even if there is additional real time processing necessary, we still want to immediately show -- cgit v1.2.3