Make sure all group/star tiles have the same height

Old way:
- Let LinearLayout divide its width for children.
  But some children may get different width than others do, if the
  width can't be equally divided.
- Children always use the given width as height.
- So sometimes some children have different height from others.

New way:
- Do the measurement and layout by ourselves.

Bug 5149952

Change-Id: If0421138ab6d8f85fb4391fbd56dc8a75ba0ef33
diff --git a/src/com/android/contacts/list/ContactTileAdapter.java b/src/com/android/contacts/list/ContactTileAdapter.java
index b7434b1..50c2c5f 100644
--- a/src/com/android/contacts/list/ContactTileAdapter.java
+++ b/src/com/android/contacts/list/ContactTileAdapter.java
@@ -21,7 +21,6 @@
 import com.android.contacts.ContactTileLoaderFactory;
 import com.android.contacts.GroupMemberLoader;
 import com.android.contacts.R;
-import com.android.contacts.list.ContactTileAdapter.DisplayType;
 
 import android.content.ContentUris;
 import android.content.Context;
@@ -34,7 +33,7 @@
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.BaseAdapter;
-import android.widget.LinearLayout;
+import android.widget.FrameLayout;
 import android.widget.TextView;
 
 import java.util.ArrayList;
@@ -482,8 +481,10 @@
 
     /**
      * Acts as a row item composed of {@link ContactTileView}
+     *
+     * TODO: FREQUENT doesn't really need it.  Just let {@link #getView} return
      */
-    private class ContactTileRow extends LinearLayout {
+    private class ContactTileRow extends FrameLayout {
         private int mItemViewType;
         private int mLayoutResId;
 
@@ -512,8 +513,11 @@
 
             if (getChildCount() <= childIndex) {
                 contactTile = (ContactTileView) inflate(mContext, mLayoutResId, null);
-                contactTile.setLayoutParams(new LinearLayout.LayoutParams(0,
-                        LinearLayout.LayoutParams.WRAP_CONTENT, 1.0f));
+                // Note: the layoutparam set here is only actually used for FREQUENT.
+                // We override onMeasure() for STARRED and we don't care the layout param there.
+                contactTile.setLayoutParams(new FrameLayout.LayoutParams(
+                        ViewGroup.LayoutParams.WRAP_CONTENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT));
                 contactTile.setPhotoManager(mPhotoManager);
                 contactTile.setListener(mContactTileListener);
                 addView(contactTile);
@@ -538,6 +542,89 @@
                     break;
             }
         }
+
+        @Override
+        protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+            switch (mItemViewType) {
+                case ViewTypes.STARRED_WITH_SECONDARY_ACTION:
+                case ViewTypes.STARRED:
+                    onLayoutForTiles(left, top, right, bottom);
+                    return;
+                default:
+                    super.onLayout(changed, left, top, right, bottom);
+                    return;
+            }
+        }
+
+        private void onLayoutForTiles(int left, int top, int right, int bottom) {
+            final int count = getChildCount();
+            final int width = right - left;
+
+            // Just line up children horizontally.
+            int childLeft = 0;
+            for (int i = 0; i < count; i++) {
+                final View child = getChildAt(i);
+
+                // Note MeasuredWidth includes the padding.
+                final int childWidth = child.getMeasuredWidth();
+                child.layout(childLeft, 0, childLeft + childWidth, child.getMeasuredHeight());
+                childLeft += childWidth;
+            }
+        }
+
+        @Override
+        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+            switch (mItemViewType) {
+                case ViewTypes.STARRED_WITH_SECONDARY_ACTION:
+                case ViewTypes.STARRED:
+                    onMeasureForTiles(widthMeasureSpec, heightMeasureSpec);
+                    return;
+                default:
+                    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+                    return;
+            }
+        }
+
+        private void onMeasureForTiles(int widthMeasureSpec, int heightMeasureSpec) {
+            final int width = MeasureSpec.getSize(widthMeasureSpec);
+
+            final int childCount = getChildCount();
+            if (childCount == 0) {
+                // Just in case...
+                setMeasuredDimension(width, 0);
+                return;
+            }
+
+            // 1. Calculate image size.
+            //      = ([total width] - [total padding]) / [child count]
+            //
+            // 2. Set it to width/height of each children.
+            //    If we have a remainder, some tiles will have 1 pixel larger width than its height.
+            //
+            // 3. Set the dimensions of itself.
+            //    Let width = given width.
+            //    Let height = image size + bottom paddding.
+
+            final int totalPaddingsInPixels = (mColumnCount - 1) * mPaddingInPixels;
+
+            // Preferred width / height for images (excluding the padding).
+            // The actual width may be 1 pixel larger than this if we have a remainder.
+            final int imageSize = (width - totalPaddingsInPixels) / mColumnCount;
+            final int remainder = width - (imageSize * mColumnCount) - totalPaddingsInPixels;
+
+            for (int i = 0; i < childCount; i++) {
+                final View child = getChildAt(i);
+                final int childWidth = imageSize + child.getPaddingRight()
+                        // Compensate for the remainder
+                        + (i < remainder ? 1 : 0);
+                final int childHeight = imageSize + child.getPaddingBottom();
+                child.measure(
+                        MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY),
+                        MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY)
+                        );
+            }
+            setMeasuredDimension(width, imageSize + getChildAt(0).getPaddingBottom());
+        }
     }
 
     /**
diff --git a/src/com/android/contacts/list/ContactTileStarredView.java b/src/com/android/contacts/list/ContactTileStarredView.java
index 3c0ba42..c017731 100644
--- a/src/com/android/contacts/list/ContactTileStarredView.java
+++ b/src/com/android/contacts/list/ContactTileStarredView.java
@@ -20,7 +20,11 @@
 
 /**
  * A {@link ContactTileStarredView} displays the contact's picture overlayed with their name
- * in a perfect square.
+ * in a square.  The actual dimensions are set by
+ * {@link com.android.contacts.list.ContactTileAdapter.ContactTileRow}.
+ *
+ * TODO Just remove this class.  We probably don't need {@link ContactTileSecondaryTargetView}
+ * either.  (We can probably put the functionality to {@link ContactTileView})
  */
 public class ContactTileStarredView extends ContactTileView {
     private final static String TAG = ContactTileStarredView.class.getSimpleName();
@@ -28,14 +32,4 @@
     public ContactTileStarredView(Context context, AttributeSet attrs) {
         super(context, attrs);
     }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        // Getting how much space is currently available and telling our
-        // Children to split it.
-        int width = getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec);
-        int childMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
-        measureChildren(childMeasureSpec, childMeasureSpec);
-        setMeasuredDimension(width, width);
-    }
 }