From 81a7b490ebbf2d9a4213a79b52e7b999aa076b7f Mon Sep 17 00:00:00 2001 From: zachh Date: Thu, 7 Dec 2017 17:59:20 -0800 Subject: Implemented PhoneLookupDataSource#onSuccesfulFill. Required adding applyBatch functionality to PhoneLookupHistoryContentProvider so that the updates can be performed in a transaction. This code was just copied and modified from AnnotatedCallLogContentProvider. Also removed the trigger which limited the size of the PhoneLookupHistory, since we now delete rows from PhoneLookupHistory when the last occurrence of a number is deleted from AnnotatedCallLog. Since AnnotatedCallLog is bounded to 1000 rows PhoneLookupHistory is now indirectly bounded by that as well. Bug: 34672501 Test: unit PiperOrigin-RevId: 178323464 Change-Id: I233163fe70641b0e4b1d4c5c0e8970ad0b4b167d --- .../database/AnnotatedCallLogContentProvider.java | 40 ++++--- .../phonelookup/PhoneLookupDataSource.java | 117 ++++++++++++++++++++- 2 files changed, 135 insertions(+), 22 deletions(-) (limited to 'java/com/android/dialer/calllog') diff --git a/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java b/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java index 825e84f91..2427624a4 100644 --- a/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java +++ b/java/com/android/dialer/calllog/database/AnnotatedCallLogContentProvider.java @@ -245,12 +245,12 @@ public class AnnotatedCallLogContentProvider extends ContentProvider { throw new IllegalArgumentException("Unknown uri: " + uri); } int rows = database.delete(AnnotatedCallLog.TABLE, selection, selectionArgs); - if (rows > 0) { - if (!isApplyingBatch()) { - notifyChange(uri); - } - } else { + if (rows == 0) { LogUtil.w("AnnotatedCallLogContentProvider.delete", "no rows deleted"); + return rows; + } + if (!isApplyingBatch()) { + notifyChange(uri); } return rows; } @@ -268,7 +268,15 @@ public class AnnotatedCallLogContentProvider extends ContentProvider { int match = uriMatcher.match(uri); switch (match) { case ANNOTATED_CALL_LOG_TABLE_CODE: - break; + int rows = database.update(AnnotatedCallLog.TABLE, values, selection, selectionArgs); + if (rows == 0) { + LogUtil.w("AnnotatedCallLogContentProvider.update", "no rows updated"); + return rows; + } + if (!isApplyingBatch()) { + notifyChange(uri); + } + return rows; case ANNOTATED_CALL_LOG_TABLE_ID_CODE: Assert.checkArgument( !values.containsKey(AnnotatedCallLog._ID), "Do not specify _ID when updating by ID"); @@ -276,23 +284,21 @@ public class AnnotatedCallLogContentProvider extends ContentProvider { Assert.checkArgument( selectionArgs == null, "Do not specify selection args when updating by ID"); selection = getSelectionWithId(ContentUris.parseId(uri)); - break; + rows = database.update(AnnotatedCallLog.TABLE, values, selection, selectionArgs); + if (rows == 0) { + LogUtil.w("AnnotatedCallLogContentProvider.update", "no rows updated"); + return rows; + } + if (!isApplyingBatch()) { + notifyChange(uri); + } + return rows; case ANNOTATED_CALL_LOG_TABLE_DISTINCT_NUMBER_CODE: - throw new UnsupportedOperationException(); case COALESCED_ANNOTATED_CALL_LOG_TABLE_CODE: throw new UnsupportedOperationException(); default: throw new IllegalArgumentException("Unknown uri: " + uri); } - int rows = database.update(AnnotatedCallLog.TABLE, values, selection, selectionArgs); - if (rows > 0) { - if (!isApplyingBatch()) { - notifyChange(uri); - } - } else { - LogUtil.w("AnnotatedCallLogContentProvider.update", "no rows updated"); - } - return rows; } /** diff --git a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java index 17a09ce47..fa7d3be16 100644 --- a/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java +++ b/java/com/android/dialer/calllog/datasources/phonelookup/PhoneLookupDataSource.java @@ -16,9 +16,13 @@ package com.android.dialer.calllog.datasources.phonelookup; +import android.content.ContentProviderOperation; import android.content.ContentValues; import android.content.Context; +import android.content.OperationApplicationException; import android.database.Cursor; +import android.net.Uri; +import android.os.RemoteException; import android.support.annotation.MainThread; import android.support.annotation.WorkerThread; import android.text.TextUtils; @@ -34,6 +38,7 @@ import com.android.dialer.common.concurrent.Annotations.LightweightExecutor; import com.android.dialer.phonelookup.PhoneLookup; import com.android.dialer.phonelookup.PhoneLookupInfo; import com.android.dialer.phonelookup.PhoneLookupSelector; +import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract; import com.android.dialer.phonelookup.database.contract.PhoneLookupHistoryContract.PhoneLookupHistory; import com.android.dialer.phonenumberproto.DialerPhoneNumberUtil; import com.google.common.collect.ImmutableMap; @@ -44,6 +49,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.i18n.phonenumbers.PhoneNumberUtil; import com.google.protobuf.InvalidProtocolBufferException; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Map; @@ -62,6 +68,23 @@ public final class PhoneLookupDataSource implements CallLogDataSource { private final ListeningExecutorService backgroundExecutorService; private final ListeningExecutorService lightweightExecutorService; + /** + * Keyed by normalized number (the primary key for PhoneLookupHistory). + * + *

This is state saved between the {@link #fill(Context, CallLogMutations)} and {@link + * #onSuccessfulFill(Context)} operations. + */ + private final Map phoneLookupHistoryRowsToUpdate = new ArrayMap<>(); + + /** + * Normalized numbers (the primary key for PhoneLookupHistory) which should be deleted from + * PhoneLookupHistory. + * + *

This is state saved between the {@link #fill(Context, CallLogMutations)} and {@link + * #onSuccessfulFill(Context)} operations. + */ + private final Set phoneLookupHistoryRowsToDelete = new ArraySet<>(); + @Inject PhoneLookupDataSource( PhoneLookup phoneLookup, @@ -108,6 +131,11 @@ public final class PhoneLookupDataSource implements CallLogDataSource { */ @Override public ListenableFuture fill(Context appContext, CallLogMutations mutations) { + // Clear state saved since the last call to fill. This is necessary in case fill is called but + // onSuccessfulFill is not called during a previous flow. + phoneLookupHistoryRowsToUpdate.clear(); + phoneLookupHistoryRowsToDelete.clear(); + // First query information from annotated call log. ListenableFuture>> annotatedCallLogIdsByNumberFuture = backgroundExecutorService.submit(() -> queryIdAndNumberFromAnnotatedCallLog(appContext)); @@ -150,6 +178,13 @@ public final class PhoneLookupDataSource implements CallLogDataSource { } populateInserts(originalPhoneLookupHistoryDataByAnnotatedCallLogId.build(), mutations); + // Compute and save the PhoneLookupHistory rows which can be deleted in onSuccessfulFill. + DialerPhoneNumberUtil dialerPhoneNumberUtil = + new DialerPhoneNumberUtil(PhoneNumberUtil.getInstance()); + phoneLookupHistoryRowsToDelete.addAll( + computePhoneLookupHistoryRowsToDelete( + annotatedCallLogIdsByNumber, mutations, dialerPhoneNumberUtil)); + // Now compute the rows to update. ImmutableMap.Builder rowsToUpdate = ImmutableMap.builder(); for (Entry entry : updatedInfoMap.entrySet()) { @@ -159,6 +194,10 @@ public final class PhoneLookupDataSource implements CallLogDataSource { for (Long id : annotatedCallLogIdsByNumber.get(dialerPhoneNumber)) { rowsToUpdate.put(id, upToDateInfo); } + // Also save the updated information so that it can be written to PhoneLookupHistory + // in onSuccessfulFill. + String normalizedNumber = dialerPhoneNumberUtil.formatToE164(dialerPhoneNumber); + phoneLookupHistoryRowsToUpdate.put(normalizedNumber, upToDateInfo); } } return rowsToUpdate.build(); @@ -167,9 +206,11 @@ public final class PhoneLookupDataSource implements CallLogDataSource { ListenableFuture> rowsToUpdateFuture = Futures.whenAllSucceed( annotatedCallLogIdsByNumberFuture, updatedInfoMapFuture, originalInfoMapFuture) - .call(computeRowsToUpdate, lightweightExecutorService); + .call( + computeRowsToUpdate, + backgroundExecutorService /* PhoneNumberUtil may do disk IO */); - // Finally apply the computed rows to update to mutations. + // Finally update the mutations with the computed rows. return Futures.transform( rowsToUpdateFuture, rowsToUpdate -> { @@ -181,12 +222,38 @@ public final class PhoneLookupDataSource implements CallLogDataSource { @Override public ListenableFuture onSuccessfulFill(Context appContext) { - return backgroundExecutorService.submit(this::onSuccessfulFillInternal); + // First update and/or delete the appropriate rows in PhoneLookupHistory. + ListenableFuture writePhoneLookupHistory = + backgroundExecutorService.submit(() -> writePhoneLookupHistory(appContext)); + + // If that succeeds, delegate to the composite PhoneLookup to notify all PhoneLookups that both + // the AnnotatedCallLog and PhoneLookupHistory have been successfully updated. + return Futures.transformAsync( + writePhoneLookupHistory, + unused -> phoneLookup.onSuccessfulBulkUpdate(), + lightweightExecutorService); } @WorkerThread - private Void onSuccessfulFillInternal() { - // TODO(zachh): Update PhoneLookupHistory. + private Void writePhoneLookupHistory(Context appContext) + throws RemoteException, OperationApplicationException { + ArrayList operations = new ArrayList<>(); + long currentTimestamp = System.currentTimeMillis(); + for (Entry entry : phoneLookupHistoryRowsToUpdate.entrySet()) { + String normalizedNumber = entry.getKey(); + PhoneLookupInfo phoneLookupInfo = entry.getValue(); + ContentValues contentValues = new ContentValues(); + contentValues.put(PhoneLookupHistory.PHONE_LOOKUP_INFO, phoneLookupInfo.toByteArray()); + contentValues.put(PhoneLookupHistory.LAST_MODIFIED, currentTimestamp); + operations.add( + ContentProviderOperation.newUpdate(numberUri(normalizedNumber)) + .withValues(contentValues) + .build()); + } + for (String normalizedNumber : phoneLookupHistoryRowsToDelete) { + operations.add(ContentProviderOperation.newDelete(numberUri(normalizedNumber)).build()); + } + appContext.getContentResolver().applyBatch(PhoneLookupHistoryContract.AUTHORITY, operations); return null; } @@ -422,7 +489,47 @@ public final class PhoneLookupDataSource implements CallLogDataSource { } } + private Set computePhoneLookupHistoryRowsToDelete( + Map> annotatedCallLogIdsByNumber, + CallLogMutations mutations, + DialerPhoneNumberUtil dialerPhoneNumberUtil) { + if (mutations.getDeletes().isEmpty()) { + return ImmutableSet.of(); + } + // First convert the dialer phone numbers to normalized numbers; we need to combine entries + // because different DialerPhoneNumbers can map to the same normalized number. + Map> idsByNormalizedNumber = new ArrayMap<>(); + for (Entry> entry : annotatedCallLogIdsByNumber.entrySet()) { + DialerPhoneNumber dialerPhoneNumber = entry.getKey(); + Set idsForDialerPhoneNumber = entry.getValue(); + String normalizedNumber = dialerPhoneNumberUtil.formatToE164(dialerPhoneNumber); + Set idsForNormalizedNumber = idsByNormalizedNumber.get(normalizedNumber); + if (idsForNormalizedNumber == null) { + idsForNormalizedNumber = new ArraySet<>(); + idsByNormalizedNumber.put(normalizedNumber, idsForNormalizedNumber); + } + idsForNormalizedNumber.addAll(idsForDialerPhoneNumber); + } + // Now look through and remove all IDs that were scheduled for delete; after doing that, if + // there are no remaining IDs left for a normalized number, the number can be deleted from + // PhoneLookupHistory. + Set normalizedNumbersToDelete = new ArraySet<>(); + for (Entry> entry : idsByNormalizedNumber.entrySet()) { + String normalizedNumber = entry.getKey(); + Set idsForNormalizedNumber = entry.getValue(); + idsForNormalizedNumber.removeAll(mutations.getDeletes()); + if (idsForNormalizedNumber.isEmpty()) { + normalizedNumbersToDelete.add(normalizedNumber); + } + } + return normalizedNumbersToDelete; + } + private static String selectName(PhoneLookupInfo phoneLookupInfo) { return PhoneLookupSelector.selectName(phoneLookupInfo); } + + private static Uri numberUri(String number) { + return PhoneLookupHistory.CONTENT_URI.buildUpon().appendEncodedPath(number).build(); + } } -- cgit v1.2.3