Merge "Optimizing contact list scrolling"
diff --git a/src/com/android/contacts/list/ContactEntryListAdapter.java b/src/com/android/contacts/list/ContactEntryListAdapter.java
index f71fd09..2d94eb0 100644
--- a/src/com/android/contacts/list/ContactEntryListAdapter.java
+++ b/src/com/android/contacts/list/ContactEntryListAdapter.java
@@ -31,6 +31,7 @@
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
+import android.widget.ListView;
 import android.widget.TextView;
 
 import java.util.HashSet;
@@ -63,6 +64,25 @@
 
     private boolean mSelectionVisible;
 
+    /**
+     * An item view is displayed differently depending on whether it is placed
+     * at the beginning, middle or end of a section. It also needs to know the
+     * section header when it is at the beginning of a section. This object
+     * captures all this configuration.
+     */
+    public static final class Placement {
+        private int position = ListView.INVALID_POSITION;
+        public boolean firstInSection;
+        public boolean lastInSection;
+        public String sectionHeader;
+
+        public void invalidate() {
+            position = ListView.INVALID_POSITION;
+        }
+    }
+
+    private Placement mPlacementCache = new Placement();
+
     public ContactEntryListAdapter(Context context) {
         super(context, R.layout.list_section, R.id.header_text);
         addPartitions();
@@ -256,6 +276,8 @@
 
     @Override
     public void changeCursor(int partitionIndex, Cursor cursor) {
+        mPlacementCache.invalidate();
+
         Partition partition = getPartition(partitionIndex);
         if (partition instanceof DirectoryPartition) {
             ((DirectoryPartition)partition).setLoading(false);
@@ -292,6 +314,54 @@
         }
     }
 
+    /**
+     * Computes the item's placement within its section and populates the {@code placement}
+     * object accordingly.  Please note that the returned object is volatile and should be
+     * copied if the result needs to be used later.
+     */
+    public Placement getItemPlacementInSection(int position) {
+        if (mPlacementCache.position == position) {
+            return mPlacementCache;
+        }
+
+        mPlacementCache.position = position;
+        if (isSectionHeaderDisplayEnabled()) {
+            int section = getSectionForPosition(position);
+            if (section != -1 && getPositionForSection(section) == position) {
+                mPlacementCache.firstInSection = true;
+                mPlacementCache.sectionHeader = (String)getSections()[section];
+            } else {
+                mPlacementCache.firstInSection = false;
+                mPlacementCache.sectionHeader = null;
+            }
+
+            mPlacementCache.lastInSection = (getPositionForSection(section + 1) - 1 == position);
+        } else {
+            mPlacementCache.firstInSection = false;
+            mPlacementCache.lastInSection = false;
+            mPlacementCache.sectionHeader = null;
+        }
+        return mPlacementCache;
+    }
+
+    @Override
+    public int getViewTypeCount() {
+        // We need a separate view type for each item type, plus another one for
+        // each type with header, plus one for "other".
+        return super.getItemViewTypeCount() * 2 + 1;
+    }
+
+    @Override
+    public int getItemViewType(int position) {
+        int type = super.getItemViewType(position);
+        if (isSectionHeaderDisplayEnabled()) {
+            Placement placement = getItemPlacementInSection(position);
+            return placement.firstInSection ? type : getItemViewTypeCount() + type;
+        } else {
+            return type;
+        }
+    }
+
     @Override
     public boolean isEmpty() {
         // TODO
diff --git a/src/com/android/contacts/list/ContactListAdapter.java b/src/com/android/contacts/list/ContactListAdapter.java
index fd949ec..cb1e63f 100644
--- a/src/com/android/contacts/list/ContactListAdapter.java
+++ b/src/com/android/contacts/list/ContactListAdapter.java
@@ -166,26 +166,9 @@
     }
 
     protected void bindSectionHeaderAndDivider(ContactListItemView view, int position) {
-        if (isSectionHeaderDisplayEnabled()) {
-            final int section = getSectionForPosition(position);
-            if (section != -1 && getPositionForSection(section) == position) {
-                String title = (String)getSections()[section];
-                view.setSectionHeader(title);
-            } else {
-                view.setDividerVisible(false);
-                view.setSectionHeader(null);
-            }
-
-            // move the divider for the last item in a section
-            if (getPositionForSection(section + 1) - 1 == position) {
-                view.setDividerVisible(false);
-            } else {
-                view.setDividerVisible(true);
-            }
-        } else {
-            view.setSectionHeader(null);
-            view.setDividerVisible(true);
-        }
+        Placement placement = getItemPlacementInSection(position);
+        view.setSectionHeader(placement.firstInSection ? placement.sectionHeader : null);
+        view.setDividerVisible(!placement.lastInSection);
     }
 
     protected void bindPhoto(final ContactListItemView view, Cursor cursor) {
diff --git a/src/com/android/contacts/list/ContactListItemView.java b/src/com/android/contacts/list/ContactListItemView.java
index 102497a..755fed7 100644
--- a/src/com/android/contacts/list/ContactListItemView.java
+++ b/src/com/android/contacts/list/ContactListItemView.java
@@ -853,4 +853,12 @@
             requestLayout();
         }
     }
+
+    @Override
+    public void requestLayout() {
+        // We will assume that once measured this will not need to resize
+        // itself, so there is no need to pass the layout request to the parent
+        // view (ListView).
+        forceLayout();
+    }
 }
diff --git a/src/com/android/contacts/list/JoinContactListAdapter.java b/src/com/android/contacts/list/JoinContactListAdapter.java
index 83c5da2..09b8caf 100644
--- a/src/com/android/contacts/list/JoinContactListAdapter.java
+++ b/src/com/android/contacts/list/JoinContactListAdapter.java
@@ -141,8 +141,8 @@
     }
 
     @Override
-    public int getViewTypeCount() {
-        return super.getViewTypeCount() + 1;
+    public int getItemViewTypeCount() {
+        return super.getItemViewTypeCount() + 1;
     }
 
     @Override
diff --git a/src/com/android/contacts/list/MultiplePhonePickerAdapter.java b/src/com/android/contacts/list/MultiplePhonePickerAdapter.java
index 2b1196b..1eba085 100644
--- a/src/com/android/contacts/list/MultiplePhonePickerAdapter.java
+++ b/src/com/android/contacts/list/MultiplePhonePickerAdapter.java
@@ -229,7 +229,7 @@
     }
 
     @Override
-    public int getViewTypeCount() {
+    public int getItemViewTypeCount() {
         return 2;
     }
 
diff --git a/src/com/android/contacts/widget/CompositeCursorAdapter.java b/src/com/android/contacts/widget/CompositeCursorAdapter.java
index 147ed42..e3a32c8 100644
--- a/src/com/android/contacts/widget/CompositeCursorAdapter.java
+++ b/src/com/android/contacts/widget/CompositeCursorAdapter.java
@@ -264,14 +264,18 @@
         return position;
     }
 
-    /**
-     * Returns the overall number of view types across all partitions. An implementation
-     * of this method needs to ensure that the returned count is consistent with the
-     * values returned by {@link #getItemViewType(int,int)}.
-     */
     @Override
     public int getViewTypeCount() {
-        return 2;
+        return getItemViewTypeCount() + 1;
+    }
+
+    /**
+     * Returns the overall number of item view types across all partitions. An
+     * implementation of this method needs to ensure that the returned count is
+     * consistent with the values returned by {@link #getItemViewType(int,int)}.
+     */
+    public int getItemViewTypeCount() {
+        return 1;
     }
 
     /**
@@ -279,7 +283,7 @@
      * specified partition.
      */
     protected int getItemViewType(int partition, int position) {
-        return 0;
+        return 1;
     }
 
     @Override