From 65c21801bf47cf24e2755445545bedabc0f393bf Mon Sep 17 00:00:00 2001 From: calderwoodra Date: Wed, 25 Oct 2017 23:16:35 -0700 Subject: Cp2 Phonelookup isDirty implementation. Checks if a cp2 contact has been modified or deleted. Bug: 67605130,64099602 Test: Cp2PhoneLookupTest PiperOrigin-RevId: 173499443 Change-Id: I1fa267c05732fba09f00113232d4370b159aa735 --- .../dialer/phonelookup/cp2/Cp2PhoneLookup.java | 125 ++++++++++++++++++++- 1 file changed, 121 insertions(+), 4 deletions(-) (limited to 'java/com/android/dialer/phonelookup') diff --git a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java index 323ec7c65..a3d97c64e 100644 --- a/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java +++ b/java/com/android/dialer/phonelookup/cp2/Cp2PhoneLookup.java @@ -16,21 +16,138 @@ package com.android.dialer.phonelookup.cp2; +import android.content.Context; +import android.database.Cursor; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.Contacts; +import android.provider.ContactsContract.DeletedContacts; +import android.support.v4.util.ArraySet; import com.android.dialer.DialerPhoneNumber; +import com.android.dialer.common.concurrent.DialerExecutors; import com.android.dialer.phonelookup.PhoneLookup; import com.android.dialer.phonelookup.PhoneLookupInfo; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import java.util.Set; -/** TODO(calderwoodra) */ -final class Cp2PhoneLookup implements PhoneLookup { +/** PhoneLookup implementation for local contacts. */ +public final class Cp2PhoneLookup implements PhoneLookup { + + private final Context context; + + Cp2PhoneLookup(Context context) { + this.context = context; + } @Override public ListenableFuture isDirty( ImmutableSet phoneNumbers, long lastModified) { - // TODO(calderwoodra) - return null; + // TODO(calderwoodra): consider a different thread pool + return MoreExecutors.listeningDecorator(DialerExecutors.getLowPriorityThreadPool(context)) + .submit(() -> isDirtyInternal(phoneNumbers, lastModified)); + } + + private boolean isDirtyInternal(ImmutableSet phoneNumbers, long lastModified) { + return contactsUpdated(getContactIdsFromPhoneNumbers(phoneNumbers), lastModified) + || contactsDeleted(lastModified); + } + + /** Returns set of contact ids that correspond to {@code phoneNumbers} if the contact exists. */ + private Set getContactIdsFromPhoneNumbers(ImmutableSet phoneNumbers) { + Set contactIds = new ArraySet<>(); + try (Cursor cursor = + context + .getContentResolver() + .query( + Phone.CONTENT_URI, + new String[] {Phone.CONTACT_ID}, + columnInSetWhereStatement(Phone.NORMALIZED_NUMBER, phoneNumbers.size()), + contactIdsSelectionArgs(phoneNumbers), + null)) { + cursor.moveToPosition(-1); + while (cursor.moveToNext()) { + contactIds.add(cursor.getLong(0 /* columnIndex */)); + } + } + return contactIds; + } + + private static String[] contactIdsSelectionArgs(ImmutableSet phoneNumbers) { + String[] args = new String[phoneNumbers.size()]; + int i = 0; + for (DialerPhoneNumber phoneNumber : phoneNumbers) { + args[i++] = getNormalizedNumber(phoneNumber); + } + return args; + } + + private static String getNormalizedNumber(DialerPhoneNumber phoneNumber) { + // TODO(calderwoodra): implement normalization logic that matches contacts. + return phoneNumber.getRawInput().getNumber(); + } + + /** Returns true if any contacts were modified after {@code lastModified}. */ + private boolean contactsUpdated(Set contactIds, long lastModified) { + try (Cursor cursor = + context + .getContentResolver() + .query( + Contacts.CONTENT_URI, + new String[] {Contacts._ID}, + contactsIsDirtyWhereStatement(contactIds.size()), + contactsIsDirtySelectionArgs(lastModified, contactIds), + null)) { + return cursor.getCount() > 0; + } + } + + private static String contactsIsDirtyWhereStatement(int numberOfContactIds) { + StringBuilder where = new StringBuilder(); + // Filter to after last modified time + where.append(Contacts.CONTACT_LAST_UPDATED_TIMESTAMP).append(" > ?"); + + // Filter based only on contacts we care about + where.append(" AND ").append(columnInSetWhereStatement(Contacts._ID, numberOfContactIds)); + return where.toString(); + } + + private String[] contactsIsDirtySelectionArgs(long lastModified, Set contactIds) { + String[] args = new String[contactIds.size() + 1]; + args[0] = Long.toString(lastModified); + int i = 1; + for (Long contactId : contactIds) { + args[i++] = Long.toString(contactId); + } + return args; + } + + /** Returns true if any contacts were deleted after {@code lastModified}. */ + private boolean contactsDeleted(long lastModified) { + try (Cursor cursor = + context + .getContentResolver() + .query( + DeletedContacts.CONTENT_URI, + new String[] {DeletedContacts.CONTACT_DELETED_TIMESTAMP}, + DeletedContacts.CONTACT_DELETED_TIMESTAMP + " > ?", + new String[] {Long.toString(lastModified)}, + null)) { + return cursor.getCount() > 0; + } + } + + private static String columnInSetWhereStatement(String columnName, int setSize) { + StringBuilder where = new StringBuilder(); + where.append(columnName).append(" IN ("); + for (int i = 0; i < setSize; i++) { + if (i != 0) { + where.append(", "); + } + where.append("?"); + } + return where.append(")").toString(); } @Override -- cgit v1.2.3