diff options
author | calderwoodra <calderwoodra@google.com> | 2017-12-08 20:52:56 -0800 |
---|---|---|
committer | Copybara-Service <copybara-piper@google.com> | 2017-12-08 20:53:51 -0800 |
commit | 73b51d5771b31c932a589abc9bb0fe64c52fe102 (patch) | |
tree | 3845e7aad9e502bf5a91eb8705c8005f06990105 /java/com/android/dialer/contactsfragment | |
parent | 20ebcdca46e6be68050cd44087f0f768f5dae5c6 (diff) |
Implemented adding a new favorites contact flow NUI.
This change consists of mainly 3 things:
- Update contacts fragment to meet AddFavoriteActivity requirements
- Implement AddFavoriteActivity
- Passing the contact back to SpeedDialFragment
Bug: 36841782
Test: SpeedDialIntegrationTest
PiperOrigin-RevId: 178461265
Change-Id: Ib3a13eae311acf6ce10a94df4f2c95b9af120cff
Diffstat (limited to 'java/com/android/dialer/contactsfragment')
4 files changed, 136 insertions, 92 deletions
diff --git a/java/com/android/dialer/contactsfragment/ContactViewHolder.java b/java/com/android/dialer/contactsfragment/ContactViewHolder.java index 0597c2a7e..2730c0d38 100644 --- a/java/com/android/dialer/contactsfragment/ContactViewHolder.java +++ b/java/com/android/dialer/contactsfragment/ContactViewHolder.java @@ -18,7 +18,6 @@ package com.android.dialer.contactsfragment; import android.content.Context; import android.net.Uri; -import android.provider.ContactsContract.QuickContact; import android.support.v7.widget.RecyclerView; import android.text.TextUtils; import android.view.View; @@ -26,7 +25,7 @@ import android.view.View.OnClickListener; import android.widget.QuickContactBadge; import android.widget.TextView; import com.android.dialer.common.Assert; -import com.android.dialer.contactsfragment.ContactsFragment.ClickAction; +import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener; import com.android.dialer.logging.InteractionEvent; import com.android.dialer.logging.Logger; @@ -37,20 +36,20 @@ final class ContactViewHolder extends RecyclerView.ViewHolder implements OnClick private final TextView name; private final QuickContactBadge photo; private final Context context; - private final @ClickAction int clickAction; + private final OnContactSelectedListener onContactSelectedListener; private String headerText; private Uri contactUri; + private long contactId; - ContactViewHolder(View itemView, @ClickAction int clickAction) { + ContactViewHolder(View itemView, OnContactSelectedListener onContactSelectedListener) { super(itemView); - Assert.checkArgument(clickAction != ClickAction.INVALID, "Invalid click action."); + this.onContactSelectedListener = Assert.isNotNull(onContactSelectedListener); context = itemView.getContext(); itemView.findViewById(R.id.click_target).setOnClickListener(this); header = itemView.findViewById(R.id.header); name = itemView.findViewById(R.id.contact_name); photo = itemView.findViewById(R.id.photo); - this.clickAction = clickAction; } /** @@ -61,9 +60,11 @@ final class ContactViewHolder extends RecyclerView.ViewHolder implements OnClick * @param contactUri to be shown by the contact card on photo click. * @param showHeader if header view should be shown {@code True}, {@code False} otherwise. */ - public void bind(String headerText, String displayName, Uri contactUri, boolean showHeader) { + public void bind( + String headerText, String displayName, Uri contactUri, long contactId, boolean showHeader) { Assert.checkArgument(!TextUtils.isEmpty(displayName)); this.contactUri = contactUri; + this.contactId = contactId; this.headerText = headerText; name.setText(displayName); @@ -89,20 +90,6 @@ final class ContactViewHolder extends RecyclerView.ViewHolder implements OnClick @Override public void onClick(View v) { - switch (clickAction) { - case ClickAction.OPEN_CONTACT_CARD: - Logger.get(context) - .logInteraction(InteractionEvent.Type.OPEN_QUICK_CONTACT_FROM_CONTACTS_FRAGMENT_ITEM); - QuickContact.showQuickContact( - photo.getContext(), - photo, - contactUri, - QuickContact.MODE_LARGE, - null /* excludeMimes */); - break; - case ClickAction.INVALID: - default: - throw Assert.createIllegalStateFailException("Invalid click action."); - } + onContactSelectedListener.onContactSelected(photo, contactUri, contactId); } } diff --git a/java/com/android/dialer/contactsfragment/ContactsAdapter.java b/java/com/android/dialer/contactsfragment/ContactsAdapter.java index 8f2120cd7..44abe29da 100644 --- a/java/com/android/dialer/contactsfragment/ContactsAdapter.java +++ b/java/com/android/dialer/contactsfragment/ContactsAdapter.java @@ -29,8 +29,8 @@ import android.view.ViewGroup; import com.android.dialer.common.Assert; import com.android.dialer.common.LogUtil; import com.android.dialer.contactphoto.ContactPhotoManager; -import com.android.dialer.contactsfragment.ContactsFragment.ClickAction; import com.android.dialer.contactsfragment.ContactsFragment.Header; +import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener; import com.android.dialer.lettertile.LetterTileDrawable; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -50,7 +50,7 @@ final class ContactsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder private final ArrayMap<ContactViewHolder, Integer> holderMap = new ArrayMap<>(); private final Context context; private final @Header int header; - private final @ClickAction int clickAction; + private final OnContactSelectedListener onContactSelectedListener; // List of contact sublist headers private String[] headers = new String[0]; @@ -59,10 +59,11 @@ final class ContactsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder // Cursor with list of contacts private Cursor cursor; - ContactsAdapter(Context context, @Header int header, @ClickAction int clickAction) { + ContactsAdapter( + Context context, @Header int header, OnContactSelectedListener onContactSelectedListener) { this.context = context; this.header = header; - this.clickAction = clickAction; + this.onContactSelectedListener = Assert.isNotNull(onContactSelectedListener); } void updateCursor(Cursor cursor) { @@ -92,7 +93,8 @@ final class ContactsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder LayoutInflater.from(context).inflate(R.layout.add_contact_row, parent, false)); case CONTACT_VIEW_TYPE: return new ContactViewHolder( - LayoutInflater.from(context).inflate(R.layout.contact_row, parent, false), clickAction); + LayoutInflater.from(context).inflate(R.layout.contact_row, parent, false), + onContactSelectedListener); case UNKNOWN_VIEW_TYPE: default: throw Assert.createIllegalStateFailException("Invalid view type: " + viewType); @@ -133,7 +135,7 @@ final class ContactsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder // it to the previous element and only show the anchored header if the row elements fall into // the same sublists. boolean showHeader = position == 0 || !header.equals(getHeaderString(position - 1)); - contactViewHolder.bind(header, name, contactUri, showHeader); + contactViewHolder.bind(header, name, contactUri, getContactId(cursor), showHeader); } /** @@ -190,11 +192,15 @@ final class ContactsAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder } private static Uri getContactUri(Cursor cursor) { - long contactId = cursor.getLong(ContactsCursorLoader.CONTACT_ID); + long contactId = getContactId(cursor); String lookupKey = cursor.getString(ContactsCursorLoader.CONTACT_LOOKUP_KEY); return Contacts.getLookupUri(contactId, lookupKey); } + private static long getContactId(Cursor cursor) { + return cursor.getLong(ContactsCursorLoader.CONTACT_ID); + } + String getHeaderString(int position) { if (header != Header.NONE) { if (position == 0) { diff --git a/java/com/android/dialer/contactsfragment/ContactsCursorLoader.java b/java/com/android/dialer/contactsfragment/ContactsCursorLoader.java index a22f7eb39..e55f95149 100644 --- a/java/com/android/dialer/contactsfragment/ContactsCursorLoader.java +++ b/java/com/android/dialer/contactsfragment/ContactsCursorLoader.java @@ -18,7 +18,10 @@ package com.android.dialer.contactsfragment; import android.content.Context; import android.content.CursorLoader; +import android.net.Uri; import android.provider.ContactsContract.Contacts; +import android.text.TextUtils; +import com.android.contacts.common.preference.ContactsPreferences; /** Cursor Loader for {@link ContactsFragment}. */ final class ContactsCursorLoader extends CursorLoader { @@ -47,26 +50,52 @@ final class ContactsCursorLoader extends CursorLoader { Contacts.LOOKUP_KEY, // 4 }; - private ContactsCursorLoader(Context context, String[] contactProjection, String sortKey) { + ContactsCursorLoader(Context context, boolean hasPhoneNumbers) { super( context, - Contacts.CONTENT_URI - .buildUpon() - .appendQueryParameter(Contacts.EXTRA_ADDRESS_BOOK_INDEX, "true") - .build(), - contactProjection, - contactProjection[CONTACT_DISPLAY_NAME] + " IS NOT NULL", + buildUri(""), + getProjection(context), + getWhere(context, hasPhoneNumbers), null, - sortKey + " ASC"); + getSortKey(context) + " ASC"); } - public static ContactsCursorLoader createInstanceDisplayNamePrimary( - Context context, String sortKey) { - return new ContactsCursorLoader(context, CONTACTS_PROJECTION_DISPLAY_NAME_PRIMARY, sortKey); + private static String[] getProjection(Context context) { + ContactsPreferences contactsPrefs = new ContactsPreferences(context); + boolean displayOrderPrimary = + (contactsPrefs.getDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY); + return displayOrderPrimary + ? CONTACTS_PROJECTION_DISPLAY_NAME_PRIMARY + : CONTACTS_PROJECTION_DISPLAY_NAME_ALTERNATIVE; } - public static ContactsCursorLoader createInstanceDisplayNameAlternative( - Context context, String sortKey) { - return new ContactsCursorLoader(context, CONTACTS_PROJECTION_DISPLAY_NAME_ALTERNATIVE, sortKey); + private static String getWhere(Context context, boolean hasPhoneNumbers) { + String where = getProjection(context)[CONTACT_DISPLAY_NAME] + " IS NOT NULL"; + if (hasPhoneNumbers) { + where += " AND " + Contacts.HAS_PHONE_NUMBER + "=1"; + } + return where; + } + + private static String getSortKey(Context context) { + ContactsPreferences contactsPrefs = new ContactsPreferences(context); + boolean sortOrderPrimary = + (contactsPrefs.getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY); + return sortOrderPrimary ? Contacts.SORT_KEY_PRIMARY : Contacts.SORT_KEY_ALTERNATIVE; + } + + /** Update cursor loader to filter contacts based on the provided query. */ + public void setQuery(String query) { + setUri(buildUri(query)); + } + + private static Uri buildUri(String query) { + Uri.Builder baseUri; + if (TextUtils.isEmpty(query)) { + baseUri = Contacts.CONTENT_URI.buildUpon(); + } else { + baseUri = Contacts.CONTENT_FILTER_URI.buildUpon().appendPath(query); + } + return baseUri.appendQueryParameter(Contacts.EXTRA_ADDRESS_BOOK_INDEX, "true").build(); } } diff --git a/java/com/android/dialer/contactsfragment/ContactsFragment.java b/java/com/android/dialer/contactsfragment/ContactsFragment.java index 82b68b8ee..714739300 100644 --- a/java/com/android/dialer/contactsfragment/ContactsFragment.java +++ b/java/com/android/dialer/contactsfragment/ContactsFragment.java @@ -26,8 +26,8 @@ import android.content.Intent; import android.content.Loader; import android.content.pm.PackageManager; import android.database.Cursor; +import android.net.Uri; import android.os.Bundle; -import android.provider.ContactsContract.Contacts; import android.support.annotation.IntDef; import android.support.annotation.Nullable; import android.support.v13.app.FragmentCompat; @@ -39,12 +39,14 @@ import android.view.LayoutInflater; import android.view.View; import android.view.View.OnScrollChangeListener; import android.view.ViewGroup; +import android.widget.ImageView; import android.widget.TextView; import com.android.contacts.common.preference.ContactsPreferences; import com.android.contacts.common.preference.ContactsPreferences.ChangeListener; import com.android.dialer.common.Assert; import com.android.dialer.common.FragmentUtils; import com.android.dialer.common.LogUtil; +import com.android.dialer.contactsfragment.ContactsFragment.OnContactSelectedListener; import com.android.dialer.performancereport.PerformanceReport; import com.android.dialer.util.DialerUtils; import com.android.dialer.util.IntentUtil; @@ -62,15 +64,6 @@ public class ContactsFragment extends Fragment OnEmptyViewActionButtonClickedListener, ChangeListener { - /** IntDef to define the OnClick action for contact rows. */ - @Retention(RetentionPolicy.SOURCE) - @IntDef({ClickAction.INVALID, ClickAction.OPEN_CONTACT_CARD}) - public @interface ClickAction { - int INVALID = 0; - /** Open contact card on click. */ - int OPEN_CONTACT_CARD = 1; - } - /** An enum for the different types of headers that be inserted at position 0 in the list. */ @Retention(RetentionPolicy.SOURCE) @IntDef({Header.NONE, Header.ADD_CONTACT}) @@ -83,7 +76,7 @@ public class ContactsFragment extends Fragment public static final int READ_CONTACTS_PERMISSION_REQUEST_CODE = 1; private static final String EXTRA_HEADER = "extra_header"; - private static final String EXTRA_CLICK_ACTION = "extra_click_action"; + private static final String EXTRA_HAS_PHONE_NUMBERS = "extra_has_phone_numbers"; /** * Listen to broadcast events about permissions in order to be notified if the READ_CONTACTS @@ -104,14 +97,11 @@ public class ContactsFragment extends Fragment private ContactsAdapter adapter; private EmptyContentView emptyContentView; - private ContactsPreferences contactsPrefs; private @Header int header; - private @ClickAction int clickAction; - /** Listener for contacts list scroll state. */ - public interface OnContactsListScrolledListener { - void onContactsListScrolled(boolean isDragging); - } + private ContactsPreferences contactsPrefs; + private boolean hasPhoneNumbers; + private String query; /** * Used to get a configured instance of ContactsFragment. @@ -121,26 +111,42 @@ public class ContactsFragment extends Fragment * * <ul> * <li>{@link Header#ADD_CONTACT} to insert a header that allows users to add a contact - * <li>{@link ClickAction#OPEN_CONTACT_CARD} to open contact cards on click + * <li>Open contact cards on click * </ul> * * And for the add favorite contact screen we might use: * * <ul> * <li>{@link Header#NONE} so that all rows are contacts (i.e. no header inserted) - * <li>{@link ClickAction#SET_RESULT_AND_FINISH} to send a selected contact to the previous - * activity. + * <li>Send a selected contact to the parent activity. * </ul> * * @param header determines the type of header inserted at position 0 in the contacts list - * @param clickAction defines the on click actions on rows that represent contacts */ - public static ContactsFragment newInstance(@Header int header, @ClickAction int clickAction) { - Assert.checkArgument(clickAction != ClickAction.INVALID, "Invalid click action"); + public static ContactsFragment newInstance(@Header int header) { ContactsFragment fragment = new ContactsFragment(); Bundle args = new Bundle(); args.putInt(EXTRA_HEADER, header); - args.putInt(EXTRA_CLICK_ACTION, clickAction); + fragment.setArguments(args); + return fragment; + } + + /** + * Returns {@link ContactsFragment} with a list of contacts such that: + * + * <ul> + * <li>Each contact has a phone number + * <li>Contacts are filterable via {@link #updateQuery(String)} + * <li>There is no list header (i.e. {@link Header#NONE} + * <li>Clicking on a contact notifies the parent activity via {@link + * OnContactSelectedListener#onContactSelected(ImageView, Uri, long)}. + * </ul> + */ + public static ContactsFragment newAddFavoritesInstance() { + ContactsFragment fragment = new ContactsFragment(); + Bundle args = new Bundle(); + args.putInt(EXTRA_HEADER, Header.NONE); + args.putBoolean(EXTRA_HAS_PHONE_NUMBERS, true); fragment.setArguments(args); return fragment; } @@ -152,7 +158,21 @@ public class ContactsFragment extends Fragment contactsPrefs = new ContactsPreferences(getContext()); contactsPrefs.registerChangeListener(this); header = getArguments().getInt(EXTRA_HEADER); - clickAction = getArguments().getInt(EXTRA_CLICK_ACTION); + hasPhoneNumbers = getArguments().getBoolean(EXTRA_HAS_PHONE_NUMBERS); + } + + @Override + public void onStart() { + super.onStart(); + PermissionsUtil.registerPermissionReceiver( + getActivity(), readContactsPermissionGrantedReceiver, READ_CONTACTS); + } + + @Override + public void onStop() { + PermissionsUtil.unregisterPermissionReceiver( + getActivity(), readContactsPermissionGrantedReceiver); + super.onStop(); } @Nullable @@ -163,7 +183,9 @@ public class ContactsFragment extends Fragment fastScroller = view.findViewById(R.id.fast_scroller); anchoredHeader = view.findViewById(R.id.header); recyclerView = view.findViewById(R.id.recycler_view); - adapter = new ContactsAdapter(getContext(), header, clickAction); + adapter = + new ContactsAdapter( + getContext(), header, FragmentUtils.getParent(this, OnContactSelectedListener.class)); recyclerView.setAdapter(adapter); manager = new LinearLayoutManager(getContext()) { @@ -208,15 +230,14 @@ public class ContactsFragment extends Fragment /** @return a loader according to sort order and display order. */ @Override public Loader<Cursor> onCreateLoader(int id, Bundle args) { - boolean sortOrderPrimary = - (contactsPrefs.getSortOrder() == ContactsPreferences.SORT_ORDER_PRIMARY); - boolean displayOrderPrimary = - (contactsPrefs.getDisplayOrder() == ContactsPreferences.DISPLAY_ORDER_PRIMARY); - - String sortKey = sortOrderPrimary ? Contacts.SORT_KEY_PRIMARY : Contacts.SORT_KEY_ALTERNATIVE; - return displayOrderPrimary - ? ContactsCursorLoader.createInstanceDisplayNamePrimary(getContext(), sortKey) - : ContactsCursorLoader.createInstanceDisplayNameAlternative(getContext(), sortKey); + ContactsCursorLoader cursorLoader = new ContactsCursorLoader(getContext(), hasPhoneNumbers); + cursorLoader.setQuery(query); + return cursorLoader; + } + + public void updateQuery(String query) { + this.query = query; + getLoaderManager().restartLoader(0, null, this); } @Override @@ -266,10 +287,13 @@ public class ContactsFragment extends Fragment } String anchoredHeaderString = adapter.getHeaderString(firstCompletelyVisible); - FragmentUtils.getParentUnsafe(this, OnContactsListScrolledListener.class) - .onContactsListScrolled( - recyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING - || fastScroller.isDragStarted()); + OnContactsListScrolledListener listener = + FragmentUtils.getParent(this, OnContactsListScrolledListener.class); + if (listener != null) { + listener.onContactsListScrolled( + recyclerView.getScrollState() == RecyclerView.SCROLL_STATE_DRAGGING + || fastScroller.isDragStarted()); + } // If the user swipes to the top of the list very quickly, there is some strange behavior // between this method updating headers and adapter#onBindViewHolder updating headers. @@ -331,17 +355,15 @@ public class ContactsFragment extends Fragment } } - @Override - public void onStart() { - super.onStart(); - PermissionsUtil.registerPermissionReceiver( - getActivity(), readContactsPermissionGrantedReceiver, READ_CONTACTS); + /** Listener for contacts list scroll state. */ + public interface OnContactsListScrolledListener { + void onContactsListScrolled(boolean isDragging); } - @Override - public void onStop() { - PermissionsUtil.unregisterPermissionReceiver( - getActivity(), readContactsPermissionGrantedReceiver); - super.onStop(); + /** Listener to notify parents when a contact is selected. */ + public interface OnContactSelectedListener { + + /** Called when a contact is selected in {@link ContactsFragment}. */ + void onContactSelected(ImageView photo, Uri contactUri, long contactId); } } |