diff options
-rw-r--r-- | java/com/android/dialer/speeddial/SpeedDialAdapter.java | 30 | ||||
-rw-r--r-- | java/com/android/dialer/speeddial/SpeedDialFragment.java | 20 | ||||
-rw-r--r-- | java/com/android/dialer/speeddial/database/SpeedDialEntry.java | 8 | ||||
-rw-r--r-- | java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java | 30 | ||||
-rw-r--r-- | java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java | 11 | ||||
-rw-r--r-- | java/com/android/dialer/speeddial/loader/SpeedDialUiItemMutator.java (renamed from java/com/android/dialer/speeddial/loader/SpeedDialUiItemLoader.java) | 91 | ||||
-rw-r--r-- | java/com/android/dialer/speeddial/loader/UiItemLoaderComponent.java | 2 |
7 files changed, 160 insertions, 32 deletions
diff --git a/java/com/android/dialer/speeddial/SpeedDialAdapter.java b/java/com/android/dialer/speeddial/SpeedDialAdapter.java index 8a37e97dd..a382b1a6b 100644 --- a/java/com/android/dialer/speeddial/SpeedDialAdapter.java +++ b/java/com/android/dialer/speeddial/SpeedDialAdapter.java @@ -34,10 +34,10 @@ import com.android.dialer.speeddial.HeaderViewHolder.SpeedDialHeaderListener; import com.android.dialer.speeddial.SuggestionViewHolder.SuggestedContactsListener; import com.android.dialer.speeddial.draghelper.SpeedDialItemTouchHelperCallback.ItemTouchHelperAdapter; import com.android.dialer.speeddial.loader.SpeedDialUiItem; +import com.google.common.collect.ImmutableList; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; @@ -146,7 +146,13 @@ public final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.Vi public void setSpeedDialUiItems(List<SpeedDialUiItem> immutableSpeedDialUiItems) { speedDialUiItems = new ArrayList<>(); speedDialUiItems.addAll(immutableSpeedDialUiItems); - speedDialUiItems.sort((o1, o2) -> Boolean.compare(o2.isStarred(), o1.isStarred())); + speedDialUiItems.sort( + (o1, o2) -> { + if (o1.isStarred() && o2.isStarred()) { + return Integer.compare(o1.pinnedPosition().or(-1), o2.pinnedPosition().or(-1)); + } + return Boolean.compare(o2.isStarred(), o1.isStarred()); + }); positionToRowTypeMap.clear(); if (speedDialUiItems.isEmpty()) { return; @@ -168,6 +174,13 @@ public final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.Vi } } + public ImmutableList<SpeedDialUiItem> getSpeedDialUiItems() { + if (speedDialUiItems == null || speedDialUiItems.isEmpty()) { + return ImmutableList.of(); + } + return ImmutableList.copyOf(speedDialUiItems); + } + public SpanSizeLookup getSpanSizeLookup() { return new SpanSizeLookup() { @Override @@ -189,16 +202,9 @@ public final class SpeedDialAdapter extends RecyclerView.Adapter<RecyclerView.Vi @Override public void onItemMove(int fromPosition, int toPosition) { - if (fromPosition < toPosition) { - for (int i = fromPosition; i < toPosition && i < speedDialUiItems.size() - 1; i++) { - Collections.swap(speedDialUiItems, i, i + 1); - } - } else { - for (int i = fromPosition - 1; i > toPosition; i--) { - Collections.swap(speedDialUiItems, i, i - 1); - } - } - // TODO(calderwoodra): store pinned positions + // fromPosition/toPosition correspond to adapter position, which is off by 1 from the list + // position b/c of the favorites header. So subtract 1 here. + speedDialUiItems.add(toPosition - 1, speedDialUiItems.remove(fromPosition - 1)); notifyItemMoved(fromPosition, toPosition); } diff --git a/java/com/android/dialer/speeddial/SpeedDialFragment.java b/java/com/android/dialer/speeddial/SpeedDialFragment.java index b74c06239..b76db1cf3 100644 --- a/java/com/android/dialer/speeddial/SpeedDialFragment.java +++ b/java/com/android/dialer/speeddial/SpeedDialFragment.java @@ -36,6 +36,7 @@ import com.android.dialer.callintent.CallInitiationType; import com.android.dialer.callintent.CallIntentBuilder; import com.android.dialer.common.FragmentUtils; 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.constants.ActivityRequestCodes; @@ -54,6 +55,7 @@ import com.android.dialer.speeddial.loader.SpeedDialUiItem; import com.android.dialer.speeddial.loader.UiItemLoaderComponent; import com.android.dialer.util.IntentUtil; import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.Futures; /** * Fragment for displaying: @@ -143,7 +145,7 @@ public class SpeedDialFragment extends Fragment { speedDialLoaderListener.listen( getContext(), - UiItemLoaderComponent.get(getContext()).speedDialUiItemLoader().loadSpeedDialUiItems(), + UiItemLoaderComponent.get(getContext()).speedDialUiItemMutator().loadSpeedDialUiItems(), this::onSpeedDialUiItemListLoaded, throwable -> { throw new RuntimeException(throwable); @@ -158,7 +160,7 @@ public class SpeedDialFragment extends Fragment { speedDialLoaderListener.listen( getContext(), UiItemLoaderComponent.get(getContext()) - .speedDialUiItemLoader() + .speedDialUiItemMutator() .starContact(data.getData()), this::onSpeedDialUiItemListLoaded, throwable -> { @@ -173,7 +175,7 @@ public class SpeedDialFragment extends Fragment { // TODO(calderwoodra): Use DiffUtil to properly update and animate the change adapter.setSpeedDialUiItems( UiItemLoaderComponent.get(getContext()) - .speedDialUiItemLoader() + .speedDialUiItemMutator() .insertDuoChannels(getContext(), speedDialUiItems)); adapter.notifyDataSetChanged(); if (getActivity() != null) { @@ -187,6 +189,18 @@ public class SpeedDialFragment extends Fragment { super.onPause(); contextMenu.hideMenu(); contextMenuBackground.setVisibility(View.GONE); + Futures.addCallback( + DialerExecutorComponent.get(getContext()) + .backgroundExecutor() + .submit( + () -> { + UiItemLoaderComponent.get(getContext()) + .speedDialUiItemMutator() + .updatePinnedPosition(adapter.getSpeedDialUiItems()); + return null; + }), + new DefaultFutureCallback<>(), + DialerExecutorComponent.get(getContext()).backgroundExecutor()); } @Override diff --git a/java/com/android/dialer/speeddial/database/SpeedDialEntry.java b/java/com/android/dialer/speeddial/database/SpeedDialEntry.java index 89aed8f37..181f9eca7 100644 --- a/java/com/android/dialer/speeddial/database/SpeedDialEntry.java +++ b/java/com/android/dialer/speeddial/database/SpeedDialEntry.java @@ -20,6 +20,7 @@ import android.provider.ContactsContract.CommonDataKinds.Phone; import android.support.annotation.IntDef; import android.support.annotation.Nullable; import com.google.auto.value.AutoValue; +import com.google.common.base.Optional; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -35,6 +36,9 @@ public abstract class SpeedDialEntry { @Nullable public abstract Long id(); + /** Position the contact is pinned to in the UI. Will be absent if it hasn't be set yet. */ + public abstract Optional<Integer> pinnedPosition(); + /** @see {@link Contacts#_ID} */ public abstract long contactId(); @@ -53,7 +57,7 @@ public abstract class SpeedDialEntry { public abstract Builder toBuilder(); public static Builder builder() { - return new AutoValue_SpeedDialEntry.Builder(); + return new AutoValue_SpeedDialEntry.Builder().setPinnedPosition(Optional.absent()); } /** Builder class for speed dial entry. */ @@ -62,6 +66,8 @@ public abstract class SpeedDialEntry { public abstract Builder setId(Long id); + public abstract Builder setPinnedPosition(Optional<Integer> pinnedPosition); + public abstract Builder setContactId(long contactId); public abstract Builder setLookupKey(String lookupKey); diff --git a/java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java b/java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java index 544bb3613..1416a203d 100644 --- a/java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java +++ b/java/com/android/dialer/speeddial/database/SpeedDialEntryDatabaseHelper.java @@ -25,6 +25,7 @@ import android.text.TextUtils; import com.android.dialer.common.Assert; import com.android.dialer.common.database.Selection; import com.android.dialer.speeddial.database.SpeedDialEntry.Channel; +import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.util.ArrayList; @@ -38,12 +39,20 @@ import java.util.List; public final class SpeedDialEntryDatabaseHelper extends SQLiteOpenHelper implements SpeedDialEntryDao { + /** + * If the pinned position is absent, then we need to write an impossible value in the table like + * -1 so that it doesn't default to 0. When we read this value from the table, we'll translate it + * to Optional.absent() in the resulting {@link SpeedDialEntry}. + */ + private static final int PINNED_POSITION_ABSENT = -1; + private static final int DATABASE_VERSION = 2; private static final String DATABASE_NAME = "CPSpeedDialEntry"; // Column names private static final String TABLE_NAME = "speed_dial_entries"; private static final String ID = "id"; + private static final String PINNED_POSITION = "pinned_position"; private static final String CONTACT_ID = "contact_id"; private static final String LOOKUP_KEY = "lookup_key"; private static final String PHONE_NUMBER = "phone_number"; @@ -53,12 +62,13 @@ public final class SpeedDialEntryDatabaseHelper extends SQLiteOpenHelper // Column positions private static final int POSITION_ID = 0; - private static final int POSITION_CONTACT_ID = 1; - private static final int POSITION_LOOKUP_KEY = 2; - private static final int POSITION_PHONE_NUMBER = 3; - private static final int POSITION_PHONE_TYPE = 4; - private static final int POSITION_PHONE_LABEL = 5; - private static final int POSITION_PHONE_TECHNOLOGY = 6; + private static final int POSITION_PINNED_POSITION = 1; + private static final int POSITION_CONTACT_ID = 2; + private static final int POSITION_LOOKUP_KEY = 3; + private static final int POSITION_PHONE_NUMBER = 4; + private static final int POSITION_PHONE_TYPE = 5; + private static final int POSITION_PHONE_LABEL = 6; + private static final int POSITION_PHONE_TECHNOLOGY = 7; // Create Table Query private static final String CREATE_TABLE_SQL = @@ -66,6 +76,7 @@ public final class SpeedDialEntryDatabaseHelper extends SQLiteOpenHelper + TABLE_NAME + " (" + (ID + " integer primary key, ") + + (PINNED_POSITION + " integer, ") + (CONTACT_ID + " integer, ") + (LOOKUP_KEY + " text, ") + (PHONE_NUMBER + " text, ") @@ -119,11 +130,17 @@ public final class SpeedDialEntryDatabaseHelper extends SQLiteOpenHelper .build(); } + Optional<Integer> pinnedPosition = Optional.of(cursor.getInt(POSITION_PINNED_POSITION)); + if (pinnedPosition.or(PINNED_POSITION_ABSENT) == PINNED_POSITION_ABSENT) { + pinnedPosition = Optional.absent(); + } + SpeedDialEntry entry = SpeedDialEntry.builder() .setDefaultChannel(channel) .setContactId(cursor.getLong(POSITION_CONTACT_ID)) .setLookupKey(cursor.getString(POSITION_LOOKUP_KEY)) + .setPinnedPosition(pinnedPosition) .setId(cursor.getLong(POSITION_ID)) .build(); entries.add(entry); @@ -226,6 +243,7 @@ public final class SpeedDialEntryDatabaseHelper extends SQLiteOpenHelper if (includeId) { values.put(ID, entry.id()); } + values.put(PINNED_POSITION, entry.pinnedPosition().or(PINNED_POSITION_ABSENT)); values.put(CONTACT_ID, entry.contactId()); values.put(LOOKUP_KEY, entry.lookupKey()); if (entry.defaultChannel() != null) { diff --git a/java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java b/java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java index 9bda3fb31..a2bdfb89a 100644 --- a/java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java +++ b/java/com/android/dialer/speeddial/loader/SpeedDialUiItem.java @@ -25,6 +25,7 @@ import com.android.dialer.common.Assert; import com.android.dialer.speeddial.database.SpeedDialEntry; import com.android.dialer.speeddial.database.SpeedDialEntry.Channel; import com.google.auto.value.AutoValue; +import com.google.common.base.Optional; import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.List; @@ -83,7 +84,9 @@ public abstract class SpeedDialUiItem { } public static Builder builder() { - return new AutoValue_SpeedDialUiItem.Builder().setChannels(ImmutableList.of()); + return new AutoValue_SpeedDialUiItem.Builder() + .setChannels(ImmutableList.of()) + .setPinnedPosition(Optional.absent()); } /** @@ -139,6 +142,7 @@ public abstract class SpeedDialUiItem { public SpeedDialEntry buildSpeedDialEntry() { return SpeedDialEntry.builder() .setId(speedDialEntryId()) + .setPinnedPosition(pinnedPosition()) .setLookupKey(lookupKey()) .setContactId(contactId()) .setDefaultChannel(defaultChannel()) @@ -212,6 +216,9 @@ public abstract class SpeedDialUiItem { @Nullable public abstract Long speedDialEntryId(); + /** @see SpeedDialEntry#pinnedPosition() */ + public abstract Optional<Integer> pinnedPosition(); + /** @see android.provider.ContactsContract.Contacts#DISPLAY_NAME */ public abstract String name(); @@ -255,6 +262,8 @@ public abstract class SpeedDialUiItem { /** Set to null if {@link #isStarred()} is false. */ public abstract Builder setSpeedDialEntryId(@Nullable Long id); + public abstract Builder setPinnedPosition(Optional<Integer> pinnedPosition); + public abstract Builder setName(String name); public abstract Builder setContactId(long contactId); diff --git a/java/com/android/dialer/speeddial/loader/SpeedDialUiItemLoader.java b/java/com/android/dialer/speeddial/loader/SpeedDialUiItemMutator.java index 921468773..5dae2efab 100644 --- a/java/com/android/dialer/speeddial/loader/SpeedDialUiItemLoader.java +++ b/java/com/android/dialer/speeddial/loader/SpeedDialUiItemMutator.java @@ -17,11 +17,14 @@ package com.android.dialer.speeddial.loader; import android.annotation.TargetApi; +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.Build.VERSION_CODES; +import android.os.RemoteException; import android.provider.ContactsContract; import android.provider.ContactsContract.CommonDataKinds.Phone; import android.provider.ContactsContract.Contacts; @@ -43,6 +46,7 @@ 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.base.Optional; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.util.concurrent.ListenableFuture; @@ -76,7 +80,7 @@ import javax.inject.Singleton; @SuppressWarnings("AndroidApiChecker") @TargetApi(VERSION_CODES.N) @Singleton -public final class SpeedDialUiItemLoader { +public final class SpeedDialUiItemMutator { private static final int MAX_DUO_SUGGESTIONS = 3; @@ -87,7 +91,7 @@ public final class SpeedDialUiItemLoader { private final ContactsPreferences contactsPreferences; @Inject - public SpeedDialUiItemLoader( + public SpeedDialUiItemMutator( @ApplicationContext Context appContext, @BackgroundExecutor ListeningExecutorService backgroundExecutor) { this.appContext = appContext; @@ -127,7 +131,7 @@ public final class SpeedDialUiItemLoader { null, null)) { if (cursor == null) { - LogUtil.e("SpeedDialUiItemLoader.insertNewContactEntry", "Cursor was null"); + LogUtil.e("SpeedDialUiItemMutator.insertNewContactEntry", "Cursor was null"); return loadSpeedDialUiItemsInternal(); } Assert.checkArgument(cursor.moveToFirst(), "Cursor should never be empty"); @@ -285,7 +289,7 @@ public final class SpeedDialUiItemLoader { null, null)) { if (cursor == null) { - LogUtil.e("SpeedDialUiItemLoader.updateContactIdsAndLookupKeys", "null cursor"); + LogUtil.e("SpeedDialUiItemMutator.updateContactIdsAndLookupKeys", "null cursor"); return new ArrayList<>(); } if (cursor.getCount() == 0) { @@ -339,9 +343,11 @@ public final class SpeedDialUiItemLoader { SpeedDialUiItem item = SpeedDialUiItem.fromCursor(cursor); for (SpeedDialEntry entry : entries) { if (entry.contactId() == item.contactId()) { - // Update the id to match it's corresponding SpeedDialEntry. + // Update the id and pinned position to match it's corresponding SpeedDialEntry. SpeedDialUiItem.Builder entrySpeedDialItem = - item.toBuilder().setSpeedDialEntryId(entry.id()); + item.toBuilder() + .setSpeedDialEntryId(entry.id()) + .setPinnedPosition(entry.pinnedPosition()); // Preserve the default channel if it didn't change/still exists Channel defaultChannel = entry.defaultChannel(); @@ -405,7 +411,7 @@ public final class SpeedDialUiItemLoader { .getContentResolver() .query(strequentUri, new String[] {Phone.CONTACT_ID}, null, null, null)) { if (cursor == null) { - LogUtil.e("SpeedDialUiItemLoader.getStrequentContacts", "null cursor"); + LogUtil.e("SpeedDialUiItemMutator.getStrequentContacts", "null cursor"); return new ArrayList<>(); } if (cursor.getCount() == 0) { @@ -430,7 +436,7 @@ public final class SpeedDialUiItemLoader { null)) { List<SpeedDialUiItem> contacts = new ArrayList<>(); if (cursor == null) { - LogUtil.e("SpeedDialUiItemLoader.getStrequentContacts", "null cursor"); + LogUtil.e("SpeedDialUiItemMutator.getStrequentContacts", "null cursor"); return new ArrayList<>(); } if (cursor.getCount() == 0) { @@ -444,6 +450,75 @@ public final class SpeedDialUiItemLoader { } /** + * Persists the position of the {@link SpeedDialUiItem items} as the pinned position according to + * the order they were passed in. + */ + @WorkerThread + public void updatePinnedPosition(List<SpeedDialUiItem> speedDialUiItems) { + Assert.isWorkerThread(); + if (speedDialUiItems == null || speedDialUiItems.isEmpty()) { + return; + } + + // Update the positions in the SpeedDialEntry database + ImmutableList.Builder<SpeedDialEntry> entriesToUpdate = ImmutableList.builder(); + for (int i = 0; i < speedDialUiItems.size(); i++) { + SpeedDialUiItem item = speedDialUiItems.get(i); + if (item.isStarred()) { + entriesToUpdate.add( + item.buildSpeedDialEntry().toBuilder().setPinnedPosition(Optional.of(i)).build()); + } + } + getSpeedDialEntryDao().update(entriesToUpdate.build()); + + // Update the positions in CP2 + // Build a list of SpeedDialUiItems where each contact is only represented once but the order + // is maintained. For example, assume you have a list of contacts with contact ids: + // > { 1, 1, 2, 1, 2, 3 } + // This list will be reduced to: + // > { 1, 2, 3 } + // and their positions in the resulting list will be written to the CP2 Contacts.PINNED column. + List<SpeedDialUiItem> cp2SpeedDialUiItems = new ArrayList<>(); + Set<Long> contactIds = new ArraySet<>(); + for (SpeedDialUiItem item : speedDialUiItems) { + if (contactIds.add(item.contactId())) { + cp2SpeedDialUiItems.add(item); + } + } + + // Code copied from PhoneFavoritesTileAdapter#handleDrop + ArrayList<ContentProviderOperation> operations = new ArrayList<>(); + for (int i = 0; i < cp2SpeedDialUiItems.size(); i++) { + SpeedDialUiItem item = cp2SpeedDialUiItems.get(i); + // Pinned positions in the database start from 1 instead of being zero-indexed like + // arrays, so offset by 1. + int databasePinnedPosition = i + 1; + if (item.pinnedPosition().isPresent() + && item.pinnedPosition().get() == databasePinnedPosition) { + continue; + } + + Uri uri = Uri.withAppendedPath(Contacts.CONTENT_URI, String.valueOf(item.contactId())); + ContentValues values = new ContentValues(); + values.put(Contacts.PINNED, databasePinnedPosition); + operations.add(ContentProviderOperation.newUpdate(uri).withValues(values).build()); + } + if (operations.isEmpty()) { + // Nothing to update + return; + } + try { + appContext.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations); + // TODO(calderwoodra): log + } catch (RemoteException | OperationApplicationException e) { + LogUtil.e( + "SpeedDialUiItemMutator.updatePinnedPosition", + "Exception thrown when pinning contacts", + e); + } + } + + /** * Returns a new list with duo reachable channels inserted. Duo channels won't replace ViLTE * channels. */ diff --git a/java/com/android/dialer/speeddial/loader/UiItemLoaderComponent.java b/java/com/android/dialer/speeddial/loader/UiItemLoaderComponent.java index 7d01b4380..852908409 100644 --- a/java/com/android/dialer/speeddial/loader/UiItemLoaderComponent.java +++ b/java/com/android/dialer/speeddial/loader/UiItemLoaderComponent.java @@ -24,7 +24,7 @@ import dagger.Subcomponent; @Subcomponent public abstract class UiItemLoaderComponent { - public abstract SpeedDialUiItemLoader speedDialUiItemLoader(); + public abstract SpeedDialUiItemMutator speedDialUiItemMutator(); public static UiItemLoaderComponent get(Context context) { return ((UiItemLoaderComponent.HasComponent) |