diff options
Diffstat (limited to 'java/com/android/dialer/speeddial/loader/SpeedDialUiItemLoader.java')
-rw-r--r-- | java/com/android/dialer/speeddial/loader/SpeedDialUiItemLoader.java | 167 |
1 files changed, 133 insertions, 34 deletions
diff --git a/java/com/android/dialer/speeddial/loader/SpeedDialUiItemLoader.java b/java/com/android/dialer/speeddial/loader/SpeedDialUiItemLoader.java index c80a06245..31494805f 100644 --- a/java/com/android/dialer/speeddial/loader/SpeedDialUiItemLoader.java +++ b/java/com/android/dialer/speeddial/loader/SpeedDialUiItemLoader.java @@ -25,21 +25,26 @@ import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.Contacts; import android.support.annotation.WorkerThread; +import android.util.ArrayMap; +import android.util.ArraySet; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.common.concurrent.Annotations.BackgroundExecutor; import com.android.dialer.common.concurrent.DialerExecutor.SuccessListener; import com.android.dialer.common.concurrent.DialerFutureSerializer; +import com.android.dialer.common.database.Selection; import com.android.dialer.inject.ApplicationContext; import com.android.dialer.speeddial.database.SpeedDialEntry; -import com.android.dialer.speeddial.database.SpeedDialEntry.Channel; import com.android.dialer.speeddial.database.SpeedDialEntryDao; import com.android.dialer.speeddial.database.SpeedDialEntryDatabaseHelper; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Set; import javax.inject.Inject; import javax.inject.Singleton; @@ -65,7 +70,7 @@ import javax.inject.Singleton; @SuppressWarnings("AndroidApiChecker") @TargetApi(VERSION_CODES.N) @Singleton -public final class SpeedDialUiItemLoader implements UiItemLoader { +public final class SpeedDialUiItemLoader { private final Context appContext; private final ListeningExecutorService backgroundExecutor; @@ -85,7 +90,6 @@ public final class SpeedDialUiItemLoader implements UiItemLoader { * list is composed of starred contacts from {@link SpeedDialEntryDatabaseHelper} and suggestions * from {@link Contacts#STREQUENT_PHONE_ONLY}. */ - @Override public ListenableFuture<ImmutableList<SpeedDialUiItem>> loadSpeedDialUiItems() { return dialerFutureSerializer.submitAsync( () -> backgroundExecutor.submit(this::doInBackground), backgroundExecutor); @@ -104,10 +108,19 @@ public final class SpeedDialUiItemLoader implements UiItemLoader { List<SpeedDialEntry> entriesToUpdate = new ArrayList<>(); List<Long> entriesToDelete = new ArrayList<>(); - // Get all SpeedDialEntries and mark them to be updated or deleted + // Get all SpeedDialEntries and update their contact ids and lookupkeys. List<SpeedDialEntry> entries = db.getAllEntries(); + entries = updateContactIdsAndLookupKeys(entries); + + // Build SpeedDialUiItems from our updated entries. + Map<SpeedDialEntry, SpeedDialUiItem> entriesToUiItems = getSpeedDialUiItemsFromEntries(entries); + Assert.checkArgument( + entries.size() == entriesToUiItems.size(), + "Updated entries are incomplete: " + entries.size() + " != " + entriesToUiItems.size()); + + // Mark the SpeedDialEntries to be updated or deleted for (SpeedDialEntry entry : entries) { - SpeedDialUiItem contact = getSpeedDialContact(entry); + SpeedDialUiItem contact = entriesToUiItems.get(entry); // Remove contacts that no longer exist or are no longer starred if (contact == null || !contact.isStarred()) { entriesToDelete.add(entry.id()); @@ -159,48 +172,108 @@ public final class SpeedDialUiItemLoader implements UiItemLoader { return ImmutableList.copyOf(speedDialUiItems); } + /** + * Returns the same list of SpeedDialEntries that are passed in except their contact ids and + * lookup keys are updated to current values. + * + * <p>Unfortunately, we need to look up each contact individually to update the contact id and + * lookup key. Luckily though, this query is highly optimized on the framework side and very + * quick. + */ + @WorkerThread + private List<SpeedDialEntry> updateContactIdsAndLookupKeys(List<SpeedDialEntry> entries) { + Assert.isWorkerThread(); + List<SpeedDialEntry> updatedEntries = new ArrayList<>(); + for (SpeedDialEntry entry : entries) { + try (Cursor cursor = + appContext + .getContentResolver() + .query( + Contacts.getLookupUri(entry.contactId(), entry.lookupKey()), + new String[] {Contacts._ID, Contacts.LOOKUP_KEY}, + null, + null, + null)) { + if (cursor == null) { + LogUtil.e("SpeedDialUiItemLoader.updateContactIdsAndLookupKeys", "null cursor"); + return new ArrayList<>(); + } + if (cursor.getCount() == 0) { + // No need to update this entry, the contact was deleted. We'll clear it up later. + updatedEntries.add(entry); + continue; + } + // Since all cursor rows will be have the same contact id and lookup key, just grab the + // first one. + cursor.moveToFirst(); + updatedEntries.add( + entry + .toBuilder() + .setContactId(cursor.getLong(0)) + .setLookupKey(cursor.getString(1)) + .build()); + } + } + return updatedEntries; + } + + /** + * Returns a map of SpeedDialEntries to their corresponding SpeedDialUiItems. Mappings to null + * elements imply that the contact was deleted. + */ @WorkerThread - private SpeedDialUiItem getSpeedDialContact(SpeedDialEntry entry) { + private Map<SpeedDialEntry, SpeedDialUiItem> getSpeedDialUiItemsFromEntries( + List<SpeedDialEntry> entries) { Assert.isWorkerThread(); - // TODO(b77725860): Might need to use the lookup uri to get the contact id first, then query - // based on that. - SpeedDialUiItem contact; + // Fetch the contact ids from the SpeedDialEntries + Set<String> contactIds = new HashSet<>(); + entries.forEach(entry -> contactIds.add(Long.toString(entry.contactId()))); + if (contactIds.isEmpty()) { + return new ArrayMap<>(); + } + + // Build SpeedDialUiItems from those contact ids and map them to their entries + Selection selection = + Selection.builder().and(Selection.column(Phone.CONTACT_ID).in(contactIds)).build(); try (Cursor cursor = appContext .getContentResolver() .query( Phone.CONTENT_URI, SpeedDialUiItem.PHONE_PROJECTION, - Phone.NUMBER + " IS NOT NULL AND " + Phone.LOOKUP_KEY + "=?", - new String[] {entry.lookupKey()}, + selection.getSelection(), + selection.getSelectionArgs(), null)) { - - if (cursor == null || cursor.getCount() == 0) { - // Contact not found, potentially deleted - LogUtil.e("SpeedDialUiItemLoader.getSpeedDialContact", "Contact not found."); - return null; + Map<SpeedDialEntry, SpeedDialUiItem> map = new ArrayMap<>(); + for (cursor.moveToFirst(); !cursor.isAfterLast(); /* Iterate in the loop */ ) { + SpeedDialUiItem item = SpeedDialUiItem.fromCursor(cursor); + for (SpeedDialEntry entry : entries) { + if (entry.contactId() == item.contactId()) { + // It's impossible for two contacts to exist with the same contact id, so if this entry + // was previously matched to a SpeedDialUiItem and is being matched again, something + // went horribly wrong. + Assert.checkArgument( + map.put(entry, item) == null, + "Each SpeedDialEntry only has one correct SpeedDialUiItem"); + } + } } - cursor.moveToFirst(); - contact = SpeedDialUiItem.fromCursor(cursor); - } - - // Preserve the default channel if it didn't change/still exists - Channel defaultChannel = entry.defaultChannel(); - if (defaultChannel != null) { - if (contact.channels().contains(defaultChannel)) { - contact = contact.toBuilder().setDefaultChannel(defaultChannel).build(); + // Contact must have been deleted + for (SpeedDialEntry entry : entries) { + map.putIfAbsent(entry, null); } + return map; } - - // TODO(calderwoodra): Consider setting the default channel if there is only one channel - return contact; } @WorkerThread private List<SpeedDialUiItem> getStrequentContacts() { Assert.isWorkerThread(); - Uri uri = + Set<String> contactIds = new ArraySet<>(); + + // Fetch the contact ids of all strequent contacts + Uri strequentUri = Contacts.CONTENT_STREQUENT_URI .buildUpon() .appendQueryParameter(ContactsContract.STREQUENT_PHONE_ONLY, "true") @@ -208,14 +281,40 @@ public final class SpeedDialUiItemLoader implements UiItemLoader { try (Cursor cursor = appContext .getContentResolver() - .query(uri, SpeedDialUiItem.PHONE_PROJECTION, null, null, null)) { + .query(strequentUri, new String[] {Phone.CONTACT_ID}, null, null, null)) { + if (cursor == null) { + LogUtil.e("SpeedDialUiItemLoader.getStrequentContacts", "null cursor"); + return new ArrayList<>(); + } + if (cursor.getCount() == 0) { + return new ArrayList<>(); + } + for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) { + contactIds.add(Long.toString(cursor.getLong(0))); + } + } + + // Build SpeedDialUiItems from those contact ids + Selection selection = + Selection.builder().and(Selection.column(Phone.CONTACT_ID).in(contactIds)).build(); + try (Cursor cursor = + appContext + .getContentResolver() + .query( + Phone.CONTENT_URI, + SpeedDialUiItem.PHONE_PROJECTION, + selection.getSelection(), + selection.getSelectionArgs(), + null)) { List<SpeedDialUiItem> contacts = new ArrayList<>(); - if (cursor == null || cursor.getCount() == 0) { + if (cursor == null) { + LogUtil.e("SpeedDialUiItemLoader.getStrequentContacts", "null cursor"); + return new ArrayList<>(); + } + if (cursor.getCount() == 0) { return contacts; } - - cursor.moveToPosition(-1); - while (cursor.moveToNext()) { + for (cursor.moveToFirst(); !cursor.isAfterLast(); /* Iterate in the loop */ ) { contacts.add(SpeedDialUiItem.fromCursor(cursor)); } return contacts; |