summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/calllog
diff options
context:
space:
mode:
authorlinyuh <linyuh@google.com>2018-05-11 15:58:37 -0700
committerCopybara-Service <copybara-piper@google.com>2018-05-11 17:00:46 -0700
commit34daf89e6a8db5b329424db85a79362536ee8245 (patch)
tree7b7a4e5b130b2dad6f9b40f428c4aa86311517c5 /java/com/android/dialer/calllog
parentc10636d87e9db5cb7c09a6e1c10eae4d7cade901 (diff)
Move coalescing logic out of AnnotatedCallLogContentProvider.
Bug: 79232964 Test: CoalescerTest, AnnotatedCallLogCursorLoaderTest, and manual testing. PiperOrigin-RevId: 196321995 Change-Id: I016bf28e0c09cf4fee5bc5a9115335fb35b7f7e9
Diffstat (limited to 'java/com/android/dialer/calllog')
-rw-r--r--java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java50
-rw-r--r--java/com/android/dialer/calllog/database/Coalescer.java151
-rw-r--r--java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java12
-rw-r--r--java/com/android/dialer/calllog/ui/AnnotatedCallLogCursorLoader.java36
-rw-r--r--java/com/android/dialer/calllog/ui/CoalescedAnnotatedCallLogCursorLoader.java132
-rw-r--r--java/com/android/dialer/calllog/ui/NewCallLogAdapter.java3
-rw-r--r--java/com/android/dialer/calllog/ui/NewCallLogFragment.java57
-rw-r--r--java/com/android/dialer/calllog/ui/NewCallLogViewHolder.java8
8 files changed, 232 insertions, 217 deletions
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.
*
* <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.
*/
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<Cursor> coalesce(
+ @NonNull Cursor allAnnotatedCallLogRowsSortedByTimestampDesc) {
+ ListenableFuture<Cursor> 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.
+ *
+ * <p>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.
+ *
+ * <p>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}.
*
- * <p>For pre-Q device, this is same as {@link TIMESTAMP}.
+ * <p>For pre-Q device, this is same as {@link #TIMESTAMP}.
*
* <p>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<ViewHolder> {
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<Cursor> 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<Cursor> 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<Cursor> 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