Updated ContactsFragment to use composition.

The following features are now configureable in ContactsFragment:
 - Presence of the "Create new contact" row at position 0
 - The on click action when a user clicks on a row in contacts

This change is being made so that this fragment can be reused in the
add favorite screen in the new favorites fragment. For more context:
https://docs.google.com/document/d/1lCjOgeYQXolrHW32Bgl-Vty_aIalQog_rog-EaO1bMA/edit#heading=h.1qre30w9h49i

Bug: 36841782
Test: existing, updated
PiperOrigin-RevId: 166089143
Change-Id: I567f4efb9c738f4fc629523e118e3cf116bf4ace
diff --git a/java/com/android/dialer/app/list/DialtactsPagerAdapter.java b/java/com/android/dialer/app/list/DialtactsPagerAdapter.java
index 822aa78..d9cb0c1 100644
--- a/java/com/android/dialer/app/list/DialtactsPagerAdapter.java
+++ b/java/com/android/dialer/app/list/DialtactsPagerAdapter.java
@@ -31,6 +31,8 @@
 import com.android.dialer.common.LogUtil;
 import com.android.dialer.configprovider.ConfigProviderBindings;
 import com.android.dialer.contactsfragment.ContactsFragment;
+import com.android.dialer.contactsfragment.ContactsFragment.ClickAction;
+import com.android.dialer.contactsfragment.ContactsFragment.Header;
 import com.android.dialer.database.CallLogQueryHandler;
 import com.android.dialer.speeddial.SpeedDialFragment;
 import com.android.dialer.util.ViewUtil;
@@ -120,7 +122,8 @@
       case TAB_INDEX_ALL_CONTACTS:
         if (useNewContactsTab) {
           if (contactsFragment == null) {
-            contactsFragment = new ContactsFragment();
+            contactsFragment =
+                ContactsFragment.newInstance(Header.ADD_CONTACT, ClickAction.OPEN_CONTACT_CARD);
           }
           return contactsFragment;
         } else {
diff --git a/java/com/android/dialer/contactsfragment/ContactViewHolder.java b/java/com/android/dialer/contactsfragment/ContactViewHolder.java
index 586e22a..0597c2a 100644
--- a/java/com/android/dialer/contactsfragment/ContactViewHolder.java
+++ b/java/com/android/dialer/contactsfragment/ContactViewHolder.java
@@ -26,6 +26,7 @@
 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.logging.InteractionEvent;
 import com.android.dialer.logging.Logger;
 
@@ -36,17 +37,20 @@
   private final TextView name;
   private final QuickContactBadge photo;
   private final Context context;
+  private final @ClickAction int clickAction;
 
   private String headerText;
   private Uri contactUri;
 
-  public ContactViewHolder(View itemView) {
+  ContactViewHolder(View itemView, @ClickAction int clickAction) {
     super(itemView);
+    Assert.checkArgument(clickAction != ClickAction.INVALID, "Invalid click action.");
     context = itemView.getContext();
     itemView.findViewById(R.id.click_target).setOnClickListener(this);
-    header = (TextView) itemView.findViewById(R.id.header);
-    name = (TextView) itemView.findViewById(R.id.contact_name);
-    photo = (QuickContactBadge) itemView.findViewById(R.id.photo);
+    header = itemView.findViewById(R.id.header);
+    name = itemView.findViewById(R.id.contact_name);
+    photo = itemView.findViewById(R.id.photo);
+    this.clickAction = clickAction;
   }
 
   /**
@@ -85,9 +89,20 @@
 
   @Override
   public void onClick(View v) {
-    Logger.get(context)
-        .logInteraction(InteractionEvent.Type.OPEN_QUICK_CONTACT_FROM_CONTACTS_FRAGMENT_ITEM);
-    QuickContact.showQuickContact(
-        photo.getContext(), photo, contactUri, QuickContact.MODE_LARGE, null /* excludeMimes */);
+    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.");
+    }
   }
 }
diff --git a/java/com/android/dialer/contactsfragment/ContactsAdapter.java b/java/com/android/dialer/contactsfragment/ContactsAdapter.java
index 1bd8e34..1389531 100644
--- a/java/com/android/dialer/contactsfragment/ContactsAdapter.java
+++ b/java/com/android/dialer/contactsfragment/ContactsAdapter.java
@@ -28,6 +28,8 @@
 import android.view.ViewGroup;
 import com.android.dialer.common.Assert;
 import com.android.dialer.contactphoto.ContactPhotoManager;
+import com.android.dialer.contactsfragment.ContactsFragment.ClickAction;
+import com.android.dialer.contactsfragment.ContactsFragment.Header;
 import com.android.dialer.lettertile.LetterTileDrawable;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -47,6 +49,8 @@
   private final ArrayMap<ContactViewHolder, Integer> holderMap = new ArrayMap<>();
   private final Context context;
   private final Cursor cursor;
+  private final @Header int header;
+  private final @ClickAction int clickAction;
 
   // List of contact sublist headers
   private final String[] headers;
@@ -54,9 +58,12 @@
   // Number of contacts that correspond to each header in {@code headers}.
   private final int[] counts;
 
-  ContactsAdapter(Context context, Cursor cursor) {
+  ContactsAdapter(
+      Context context, Cursor cursor, @Header int header, @ClickAction int clickAction) {
     this.context = context;
     this.cursor = cursor;
+    this.header = header;
+    this.clickAction = clickAction;
     headers = cursor.getExtras().getStringArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_TITLES);
     counts = cursor.getExtras().getIntArray(Contacts.EXTRA_ADDRESS_BOOK_INDEX_COUNTS);
   }
@@ -70,7 +77,7 @@
             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));
+            LayoutInflater.from(context).inflate(R.layout.contact_row, parent, false), clickAction);
       case UNKNOWN_VIEW_TYPE:
       default:
         throw Assert.createIllegalStateFailException("Invalid view type: " + viewType);
@@ -85,8 +92,10 @@
 
     ContactViewHolder contactViewHolder = (ContactViewHolder) viewHolder;
     holderMap.put(contactViewHolder, position);
-    // Cursor should be offset by 1 because of add contact row
-    cursor.moveToPosition(position - 1);
+    cursor.moveToPosition(position);
+    if (header != Header.NONE) {
+      cursor.moveToPrevious();
+    }
 
     String name = getDisplayName(cursor);
     String header = getHeaderString(position);
@@ -112,9 +121,16 @@
     contactViewHolder.bind(header, name, contactUri, showHeader);
   }
 
+  /**
+   * Returns {@link #ADD_CONTACT_VIEW_TYPE} if the adapter was initialized with {@link
+   * Header#ADD_CONTACT} and the position is 0. Otherwise, {@link #CONTACT_VIEW_TYPE}.
+   */
   @Override
   public @ContactsViewType int getItemViewType(int position) {
-    return position == 0 ? ADD_CONTACT_VIEW_TYPE : CONTACT_VIEW_TYPE;
+    if (header != Header.NONE && position == 0) {
+      return ADD_CONTACT_VIEW_TYPE;
+    }
+    return CONTACT_VIEW_TYPE;
   }
 
   @Override
@@ -125,7 +141,7 @@
     }
   }
 
-  public void refreshHeaders() {
+  void refreshHeaders() {
     for (ContactViewHolder holder : holderMap.keySet()) {
       int position = holderMap.get(holder);
       boolean showHeader =
@@ -137,7 +153,12 @@
 
   @Override
   public int getItemCount() {
-    return (cursor == null || cursor.isClosed() ? 0 : cursor.getCount()) + 1; // add contact
+    int count = cursor == null || cursor.isClosed() ? 0 : cursor.getCount();
+    // Manually insert the header if one exists.
+    if (header != Header.NONE) {
+      count++;
+    }
+    return count;
   }
 
   private static String getDisplayName(Cursor cursor) {
@@ -159,11 +180,13 @@
     return Contacts.getLookupUri(contactId, lookupKey);
   }
 
-  public String getHeaderString(int position) {
-    if (position == 0) {
-      return "+";
+  String getHeaderString(int position) {
+    if (header != Header.NONE) {
+      if (position == 0) {
+        return "+";
+      }
+      position--;
     }
-    position--;
 
     int index = -1;
     int sum = 0;
diff --git a/java/com/android/dialer/contactsfragment/ContactsFragment.java b/java/com/android/dialer/contactsfragment/ContactsFragment.java
index 50c9fe8..86ac834 100644
--- a/java/com/android/dialer/contactsfragment/ContactsFragment.java
+++ b/java/com/android/dialer/contactsfragment/ContactsFragment.java
@@ -23,6 +23,7 @@
 import android.database.Cursor;
 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;
 import android.support.v7.widget.LinearLayoutManager;
@@ -44,6 +45,8 @@
 import com.android.dialer.util.PermissionsUtil;
 import com.android.dialer.widget.EmptyContentView;
 import com.android.dialer.widget.EmptyContentView.OnEmptyViewActionButtonClickedListener;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 
 /** Fragment containing a list of all contacts. */
@@ -53,8 +56,31 @@
         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({
+  ContactsFragment.Header.NONE,
+  ContactsFragment.Header.ADD_CONTACT})
+  public @interface Header {
+    int NONE = 0;
+    /** Header that allows the user to add a new contact. */
+    int ADD_CONTACT = 1;
+  }
+
   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 FastScroller fastScroller;
   private TextView anchoredHeader;
   private RecyclerView recyclerView;
@@ -63,12 +89,49 @@
   private EmptyContentView emptyContentView;
 
   private ContactsPreferences contactsPrefs;
+  private @Header int header;
+  private @ClickAction int clickAction;
 
+  /**
+   * Used to get a configured instance of ContactsFragment.
+   *
+   * <p>Current example of this fragment are the contacts tab and in creating a new favorite
+   * contact. For example, the contacts tab we use:
+   *
+   * <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
+   * </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.
+   * </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");
+    ContactsFragment fragment = new ContactsFragment();
+    Bundle args = new Bundle();
+    args.putInt(EXTRA_HEADER, header);
+    args.putInt(EXTRA_CLICK_ACTION, clickAction);
+    fragment.setArguments(args);
+    return fragment;
+  }
+
+  @SuppressWarnings("WrongConstant")
   @Override
   public void onCreate(@Nullable Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     contactsPrefs = new ContactsPreferences(getContext());
     contactsPrefs.registerChangeListener(this);
+    header = getArguments().getInt(EXTRA_HEADER);
+    clickAction = getArguments().getInt(EXTRA_CLICK_ACTION);
   }
 
   @Nullable
@@ -126,7 +189,7 @@
     } else {
       emptyContentView.setVisibility(View.GONE);
       recyclerView.setVisibility(View.VISIBLE);
-      adapter = new ContactsAdapter(getContext(), cursor);
+      adapter = new ContactsAdapter(getContext(), cursor, header, clickAction);
       manager =
           new LinearLayoutManager(getContext()) {
             @Override