summaryrefslogtreecommitdiff
path: root/java/com/android/dialer/calllog/database
diff options
context:
space:
mode:
Diffstat (limited to 'java/com/android/dialer/calllog/database')
-rw-r--r--java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java25
-rw-r--r--java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java33
-rw-r--r--java/com/android/dialer/calllog/database/Coalescer.java52
-rw-r--r--java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java23
4 files changed, 119 insertions, 14 deletions
diff --git a/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java b/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java
index a9c0d36b0..30aa2bff5 100644
--- a/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java
+++ b/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java
@@ -41,6 +41,13 @@ import java.util.ArrayList;
/** {@link ContentProvider} for the annotated call log. */
public class AnnotatedCallLogContentProvider extends ContentProvider {
+ /**
+ * We sometimes run queries where we potentially pass every ID into a where clause using the
+ * (?,?,?,...) syntax. The maximum number of host parameters is 999, so that's the maximum size
+ * this table can be. See https://www.sqlite.org/limits.html for more details.
+ */
+ private static final int MAX_ROWS = 999;
+
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 COALESCED_ANNOTATED_CALL_LOG_TABLE_CODE = 3;
@@ -72,7 +79,7 @@ public class AnnotatedCallLogContentProvider extends ContentProvider {
@Override
public boolean onCreate() {
- databaseHelper = new AnnotatedCallLogDatabaseHelper(getContext());
+ databaseHelper = new AnnotatedCallLogDatabaseHelper(getContext(), MAX_ROWS);
coalescer = CallLogDatabaseComponent.get(getContext()).coalescer();
return true;
}
@@ -283,7 +290,21 @@ public class AnnotatedCallLogContentProvider extends ContentProvider {
throw new OperationApplicationException("error inserting row");
}
} else if (result.count == 0) {
- throw new OperationApplicationException("error updating or deleting rows");
+ /*
+ * The batches built by MutationApplier happen to contain operations in order of:
+ *
+ * 1. Inserts
+ * 2. Updates
+ * 3. Deletes
+ *
+ * Let's say the last row in the table is row Z, and MutationApplier wishes to update it,
+ * as well as insert row A. When row A gets inserted, row Z will be deleted via the
+ * trigger if the table is full. Then later, when we try to process the update for row Z,
+ * it won't exist.
+ */
+ LogUtil.w(
+ "AnnotatedCallLogContentProvider.applyBatch",
+ "update or delete failed, possibly because row got cleaned up");
}
results[i] = result;
}
diff --git a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
index 3cca639ff..887dfcbb6 100644
--- a/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
+++ b/java/com/android/dialer/calllog/database/AnnotatedCallLogDatabaseHelper.java
@@ -21,28 +21,53 @@ import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import com.android.dialer.calllog.database.contract.AnnotatedCallLogContract.AnnotatedCallLog;
import com.android.dialer.common.LogUtil;
+import java.util.Locale;
/** {@link SQLiteOpenHelper} for the AnnotatedCallLog database. */
class AnnotatedCallLogDatabaseHelper extends SQLiteOpenHelper {
+ private final int maxRows;
- AnnotatedCallLogDatabaseHelper(Context appContext) {
+ AnnotatedCallLogDatabaseHelper(Context appContext, int maxRows) {
super(appContext, "annotated_call_log.db", null, 1);
+ this.maxRows = maxRows;
}
- private static final String CREATE_SQL =
+ private static final String CREATE_TABLE_SQL =
new StringBuilder()
.append("create table if not exists " + AnnotatedCallLog.TABLE + " (")
.append(AnnotatedCallLog._ID + " integer primary key, ")
.append(AnnotatedCallLog.TIMESTAMP + " integer, ")
- .append(AnnotatedCallLog.CONTACT_NAME + " string")
+ .append(AnnotatedCallLog.CONTACT_NAME + " string, ")
+ .append(AnnotatedCallLog.NUMBER + " blob")
.append(");")
.toString();
+ /** Deletes all but the first maxRows rows (by timestamp) to keep the table a manageable size. */
+ private static final String CREATE_TRIGGER_SQL =
+ "create trigger delete_old_rows after insert on "
+ + AnnotatedCallLog.TABLE
+ + " when (select count(*) from "
+ + AnnotatedCallLog.TABLE
+ + ") > %d"
+ + " begin delete from "
+ + AnnotatedCallLog.TABLE
+ + " where "
+ + AnnotatedCallLog._ID
+ + " in (select "
+ + AnnotatedCallLog._ID
+ + " from "
+ + AnnotatedCallLog.TABLE
+ + " order by timestamp limit (select count(*)-%d"
+ + " from "
+ + AnnotatedCallLog.TABLE
+ + " )); end;";
+
@Override
public void onCreate(SQLiteDatabase db) {
LogUtil.enterBlock("AnnotatedCallLogDatabaseHelper.onCreate");
long startTime = System.currentTimeMillis();
- db.execSQL(CREATE_SQL);
+ db.execSQL(CREATE_TABLE_SQL);
+ db.execSQL(String.format(Locale.US, CREATE_TRIGGER_SQL, maxRows, maxRows));
// TODO: Consider logging impression.
LogUtil.i(
"AnnotatedCallLogDatabaseHelper.onCreate",
diff --git a/java/com/android/dialer/calllog/database/Coalescer.java b/java/com/android/dialer/calllog/database/Coalescer.java
index e3dfb7ece..5683687fd 100644
--- a/java/com/android/dialer/calllog/database/Coalescer.java
+++ b/java/com/android/dialer/calllog/database/Coalescer.java
@@ -17,15 +17,18 @@ package com.android.dialer.calllog.database;
import android.content.ContentValues;
import android.database.Cursor;
-import android.database.DatabaseUtils;
import android.database.MatrixCursor;
import android.support.annotation.NonNull;
import android.support.annotation.WorkerThread;
+import com.android.dialer.DialerPhoneNumber;
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.common.Assert;
+import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil;
+import com.google.i18n.phonenumbers.PhoneNumberUtil;
+import com.google.protobuf.InvalidProtocolBufferException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@@ -64,6 +67,9 @@ public class Coalescer {
// 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(PhoneNumberUtil.getInstance());
+
MatrixCursor allCoalescedRowsMatrixCursor =
new MatrixCursor(
CoalescedAnnotatedCallLog.ALL_COLUMNS,
@@ -75,9 +81,8 @@ public class Coalescer {
List<ContentValues> currentRowGroup = new ArrayList<>();
do {
- ContentValues currentRow = new ContentValues();
- DatabaseUtils.cursorRowToContentValues(
- allAnnotatedCallLogRowsSortedByTimestampDesc, currentRow);
+ ContentValues currentRow =
+ cursorRowToContentValues(allAnnotatedCallLogRowsSortedByTimestampDesc);
if (currentRowGroup.isEmpty()) {
currentRowGroup.add(currentRow);
@@ -86,7 +91,7 @@ public class Coalescer {
ContentValues previousRow = currentRowGroup.get(currentRowGroup.size() - 1);
- if (!rowsShouldBeCombined(previousRow, currentRow)) {
+ if (!rowsShouldBeCombined(dialerPhoneNumberUtil, previousRow, currentRow)) {
ContentValues coalescedRow = coalesceRowsForAllDataSources(currentRowGroup);
coalescedRow.put(CoalescedAnnotatedCallLog.NUMBER_CALLS, currentRowGroup.size());
addContentValuesToMatrixCursor(
@@ -104,13 +109,46 @@ public class Coalescer {
return allCoalescedRowsMatrixCursor;
}
+ 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));
+ }
+ }
+ return values;
+ }
+
/**
* @param row1 a row from {@link AnnotatedCallLog}
* @param row2 a row from {@link AnnotatedCallLog}
*/
- private static boolean rowsShouldBeCombined(ContentValues row1, ContentValues row2) {
+ private static boolean rowsShouldBeCombined(
+ DialerPhoneNumberUtil dialerPhoneNumberUtil, ContentValues row1, ContentValues row2) {
// TODO: Real implementation.
- return row1.get(AnnotatedCallLog.TIMESTAMP).equals(row2.get(AnnotatedCallLog.TIMESTAMP));
+ DialerPhoneNumber number1;
+ DialerPhoneNumber number2;
+ try {
+ number1 = DialerPhoneNumber.parseFrom(row1.getAsByteArray(AnnotatedCallLog.NUMBER));
+ number2 = DialerPhoneNumber.parseFrom(row2.getAsByteArray(AnnotatedCallLog.NUMBER));
+ } catch (InvalidProtocolBufferException e) {
+ throw Assert.createAssertionFailException("error parsing DialerPhoneNumber proto", e);
+ }
+
+ if (!number1.hasDialerInternalPhoneNumber() && !number2.hasDialerInternalPhoneNumber()) {
+ // Empty numbers should not be combined.
+ return false;
+ }
+
+ if (!number1.hasDialerInternalPhoneNumber() || !number2.hasDialerInternalPhoneNumber()) {
+ // An empty number should not be combined with a non-empty number.
+ return false;
+ }
+ return dialerPhoneNumberUtil.isExactMatch(number1, number2);
}
/**
diff --git a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java
index 8b3b0a852..7f314e37c 100644
--- a/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java
+++ b/java/com/android/dialer/calllog/database/contract/AnnotatedCallLogContract.java
@@ -66,6 +66,18 @@ public class AnnotatedCallLogContract {
/** 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";
+
+ /**
+ * The phone number called or number the call came from, encoded as a {@link
+ * com.android.dialer.DialerPhoneNumber} proto. The number may be empty if it was an incoming
+ * call and the number was unknown.
+ *
+ * <p>This column is only present in the annotated call log, and not the coalesced annotated
+ * call log. The coalesced version uses a formatted number string rather than proto bytes.
+ *
+ * <p>Type: BLOB
+ */
+ public static final String NUMBER = "number";
}
/**
@@ -96,10 +108,19 @@ public class AnnotatedCallLogContract {
public static final String NUMBER_CALLS = "number_calls";
/**
+ * The phone number formatted in a way suitable for display to the user. This value is generated
+ * on the fly when the {@link CoalescedAnnotatedCallLog} is generated.
+ *
+ * <p>Type: TEXT
+ */
+ public static final String FORMATTED_NUMBER = "formatted_number";
+
+ /**
* Columns that are only in the {@link CoalescedAnnotatedCallLog} but not the {@link
* AnnotatedCallLog}.
*/
- private static final String[] COLUMNS_ONLY_IN_COALESCED_CALL_LOG = new String[] {NUMBER_CALLS};
+ private static final String[] COLUMNS_ONLY_IN_COALESCED_CALL_LOG =
+ new String[] {NUMBER_CALLS, FORMATTED_NUMBER};
/** All columns in the {@link CoalescedAnnotatedCallLog}. */
public static final String[] ALL_COLUMNS =