Aligining fields in the editor

Change-Id: I7137055bf8ab61df33f6a87c8e6c8227dbee8a9f
diff --git a/res/layout-xlarge/item_group_membership.xml b/res/layout-xlarge/item_group_membership.xml
index c5f0dcf..1b47b9e 100644
--- a/res/layout-xlarge/item_group_membership.xml
+++ b/res/layout-xlarge/item_group_membership.xml
@@ -19,21 +19,22 @@
 <com.android.contacts.editor.GroupMembershipView
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:minHeight="?android:attr/listPreferredItemHeight"
+    android:layout_height="@dimen/editor_min_line_item_height"
     android:orientation="horizontal">
 
     <TextView
         android:id="@+id/kind_title"
         android:layout_width="150dip"
-        android:layout_height="wrap_content"
+        android:layout_height="@dimen/editor_min_line_item_height"
+        android:gravity="center_vertical"
         android:textAppearance="?android:attr/textAppearanceSmall"
         android:singleLine="true"
         android:textColor="@color/editor_label_text_color"
         android:ellipsize="marquee" />
 
+    <!-- TODO: Change to android.R.attr.spinnerTextStyle when available-->
     <TextView
-        style="?android:attr/dropDownSpinnerStyle"
+        style="?android:attr/editTextStyle"
         android:id="@+id/group_list"
         android:layout_width="0dip"
         android:layout_height="wrap_content"
@@ -43,7 +44,7 @@
         android:ellipsize="end"
         android:focusable="true"
     />
-
+    
     <!-- Plus/Minus button only for layout. This makes the editor lay out nicely with the other fields -->
     <ImageButton
         android:layout_width="wrap_content"
diff --git a/res/layout-xlarge/item_kind_section.xml b/res/layout-xlarge/item_kind_section.xml
index 0d44af6..57de26d 100644
--- a/res/layout-xlarge/item_kind_section.xml
+++ b/res/layout-xlarge/item_kind_section.xml
@@ -25,7 +25,8 @@
     <TextView
         android:id="@+id/kind_title"
         android:layout_width="150dip"
-        android:layout_height="wrap_content"
+        android:layout_height="@dimen/editor_min_line_item_height"
+        android:gravity="center_vertical"
         android:textColor="#7F7F7F"
         android:textAppearance="?android:attr/textAppearanceSmall"
         android:singleLine="true"
@@ -39,16 +40,21 @@
         android:layout_gravity="bottom"
         android:orientation="vertical" />
 
-    <ImageButton
-        android:id="@+id/kind_plus"
+    <FrameLayout
+        android:id="@+id/kind_plus_container"
         android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="bottom"
-        android:paddingLeft="@dimen/editor_round_button_padding_left"
-        android:paddingRight="@dimen/editor_round_button_padding_right"
-        android:paddingTop="@dimen/editor_round_button_padding_top"
-        android:paddingBottom="@dimen/editor_round_button_padding_bottom"
-        android:background="?android:attr/selectableItemBackground"
-        android:src="@drawable/ic_menu_add_field_holo_light"
-        android:contentDescription="@string/description_plus_button" />
+        android:layout_height="@dimen/editor_min_line_item_height">
+        <ImageButton
+            android:id="@+id/kind_plus"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="center_vertical"
+            android:paddingLeft="@dimen/editor_round_button_padding_left"
+            android:paddingRight="@dimen/editor_round_button_padding_right"
+            android:paddingTop="@dimen/editor_round_button_padding_top"
+            android:paddingBottom="@dimen/editor_round_button_padding_bottom"
+            android:background="?android:attr/selectableItemBackground"
+            android:src="@drawable/ic_menu_add_field_holo_light"
+            android:contentDescription="@string/description_plus_button" />
+    </FrameLayout>
 </com.android.contacts.editor.KindSectionView>
diff --git a/res/layout-xlarge/raw_contact_editor_view.xml b/res/layout-xlarge/raw_contact_editor_view.xml
index 07731fc..3560fa6 100644
--- a/res/layout-xlarge/raw_contact_editor_view.xml
+++ b/res/layout-xlarge/raw_contact_editor_view.xml
@@ -82,7 +82,7 @@
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
             android:layout_marginTop="10dip"
-            android:layout_marginBottom="10dip">>
+            android:layout_marginBottom="42dip">
             <Button
                 android:id="@+id/button_add_field"
                 android:text="@string/add_field"
diff --git a/res/values/dimens.xml b/res/values/dimens.xml
index 0916989..5f01cbd 100644
--- a/res/values/dimens.xml
+++ b/res/values/dimens.xml
@@ -38,11 +38,14 @@
     <dimen name="quick_contact_width">352dip</dimen>
 
     <!-- Padding of the rounded plus/minus/expand/collapse buttons in the editor  -->
-    <dimen name="editor_round_button_padding_left">10dip</dimen>
-    <dimen name="editor_round_button_padding_right">10dip</dimen>
-    <dimen name="editor_round_button_padding_top">10dip</dimen>
-    <dimen name="editor_round_button_padding_bottom">10dip</dimen>
+    <dimen name="editor_round_button_padding_left">8dip</dimen>
+    <dimen name="editor_round_button_padding_right">8dip</dimen>
+    <dimen name="editor_round_button_padding_top">8dip</dimen>
+    <dimen name="editor_round_button_padding_bottom">8dip</dimen>
 
     <!-- Width of the Type-Label in the Editor -->
     <dimen name="editor_type_label_width">180dip</dimen>
+    
+    <!-- Minimum height of a row in the Editor -->
+    <dimen name="editor_min_line_item_height">48dip</dimen>
 </resources>
diff --git a/src/com/android/contacts/editor/EventFieldEditorView.java b/src/com/android/contacts/editor/EventFieldEditorView.java
index 1d2ccdc..c386812 100644
--- a/src/com/android/contacts/editor/EventFieldEditorView.java
+++ b/src/com/android/contacts/editor/EventFieldEditorView.java
@@ -64,6 +64,18 @@
     }
 
     @Override
+    public int getBaseline(int row) {
+        int baseline = super.getBaseline(row);
+        if (mDateView != null) {
+            // The date view will be centered vertically in the corresponding line item
+            int lineItemHeight = getLineItemHeight(row);
+            int offset = (lineItemHeight - mDateView.getMeasuredHeight()) / 2;
+            baseline = Math.max(baseline, offset + mDateView.getBaseline());
+        }
+        return baseline;
+    }
+
+    @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
 
@@ -76,12 +88,20 @@
         final int labelWidth = (getLabel() != null) ? getLabel().getMeasuredWidth() : 0;
         final int deleteWidth = (getDelete() != null) ? getDelete().getMeasuredWidth() : 0;
         final int r2 = r1 - deleteWidth - labelWidth;
-        if (mDateView != null) mDateView.layout(l1, t1, r2, t1 + mDateView.getMeasuredHeight());
+        if (mDateView != null) {
+            int height = mDateView.getMeasuredHeight();
+            int baseline = getBaseline(0);
+            int top = t1 + baseline - mDateView.getBaseline();
+            mDateView.layout(
+                    l1, top,
+                    r2, top + height);
+        }
     }
 
     @Override
-    protected int getEditorHeight() {
-        return mDateView != null ? mDateView.getMeasuredHeight() : 0;
+    protected int getLineItemHeight(int row) {
+        int height = mDateView == null ? 0 : mDateView.getMeasuredHeight();
+        return Math.max(height, super.getLineItemHeight(row));
     }
 
     @Override
@@ -103,7 +123,9 @@
         super.setValues(kind, entry, state, readOnly, vig);
 
         if (mDateView == null) {
-            mDateView = new TextView(getContext(), null, android.R.attr.dropDownSpinnerStyle);
+
+            // TODO: Change to android.R.attr.spinnerTextStyle when available
+            mDateView = new TextView(getContext(), null, android.R.attr.editTextStyle);
             mDateView.setFocusable(true);
             mDateView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
                     LayoutParams.WRAP_CONTENT));
diff --git a/src/com/android/contacts/editor/KindSectionView.java b/src/com/android/contacts/editor/KindSectionView.java
index 0ece15a..1debaa2 100644
--- a/src/com/android/contacts/editor/KindSectionView.java
+++ b/src/com/android/contacts/editor/KindSectionView.java
@@ -43,6 +43,7 @@
     private static final String TAG = "KindSectionView";
 
     private ViewGroup mEditors;
+    private View mAddPlusButtonContainer;
     private ImageButton mAddPlusButton;
     private TextView mTitle;
     private String mTitleString;
@@ -53,12 +54,16 @@
 
     private ViewIdGenerator mViewIdGenerator;
 
+    private int mMinLineItemHeight;
+
     public KindSectionView(Context context) {
-        super(context);
+        this(context, null);
     }
 
     public KindSectionView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        mMinLineItemHeight = context.getResources().getDimensionPixelSize(
+                R.dimen.editor_min_line_item_height);
     }
 
     @Override
@@ -76,6 +81,24 @@
         }
     }
 
+    @Override
+    protected void onLayout(boolean changed, int l, int t, int r, int b) {
+        super.onLayout(changed, l, t, r, b);
+        if (!changed) {
+            return;
+        }
+
+        if (mAddPlusButton == null || mEditors == null || mEditors.getChildCount() < 2) {
+            return;
+        }
+
+        // Align the "+" button with the "-" button in the last editor
+        View lastEditor = mEditors.getChildAt(mEditors.getChildCount() - 1);
+        int top = lastEditor.getTop();
+        mAddPlusButtonContainer.layout(mAddPlusButtonContainer.getLeft(), top,
+                mAddPlusButtonContainer.getRight(), top + mAddPlusButtonContainer.getHeight());
+    }
+
     public boolean isReadOnly() {
         return mReadOnly;
     }
@@ -88,6 +111,7 @@
 
         mEditors = (ViewGroup)findViewById(R.id.kind_editors);
 
+        mAddPlusButtonContainer = findViewById(R.id.kind_plus_container);
         mAddPlusButton = (ImageButton) findViewById(R.id.kind_plus);
         mAddPlusButton.setOnClickListener(new OnClickListener() {
             @Override
diff --git a/src/com/android/contacts/editor/LabeledEditorView.java b/src/com/android/contacts/editor/LabeledEditorView.java
index f06191a..6d44d15 100644
--- a/src/com/android/contacts/editor/LabeledEditorView.java
+++ b/src/com/android/contacts/editor/LabeledEditorView.java
@@ -76,6 +76,7 @@
     private ViewIdGenerator mViewIdGenerator;
     private DialogManager mDialogManager = null;
     private EditorListener mListener;
+    protected int mMinLineItemHeight;
 
     /**
      * A marker in the spinner adapter of the currently selected custom type.
@@ -97,20 +98,82 @@
 
     public LabeledEditorView(Context context) {
         super(context);
+        init(context);
     }
 
     public LabeledEditorView(Context context, AttributeSet attrs) {
         super(context, attrs);
+        init(context);
     }
 
     public LabeledEditorView(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
+        init(context);
+    }
+
+    private void init(Context context) {
+        mMinLineItemHeight = context.getResources().getDimensionPixelSize(
+                R.dimen.editor_min_line_item_height);
     }
 
     public boolean isReadOnly() {
         return mReadOnly;
     }
 
+    public int getBaseline(int row) {
+        if (row == 0 && mLabel != null) {
+            return mLabel.getBaseline();
+        }
+        return -1;
+    }
+
+    /**
+     * Returns the number of rows in this editor, including the invisible ones.
+     */
+    protected int getLineItemCount() {
+        return 1;
+    }
+
+    protected boolean isLineItemVisible(int row) {
+        return true;
+    }
+
+    protected int getLineItemHeight(int row) {
+        int fieldHeight = 0;
+        int buttonHeight = 0;
+        if (row == 0) {
+            // summarize the EditText heights
+            if (mLabel != null) {
+                fieldHeight = mLabel.getMeasuredHeight();
+            }
+
+            // Ensure there is enough space for the minus button
+            View deleteButton = getDelete();
+            final int deleteHeight = (deleteButton != null) ? deleteButton.getMeasuredHeight() : 0;
+            buttonHeight += deleteHeight;
+        }
+
+        return Math.max(Math.max(buttonHeight, fieldHeight), mMinLineItemHeight);
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        measureChildren(widthMeasureSpec, heightMeasureSpec);
+
+        int height = 0;
+        height += getPaddingTop() + getPaddingBottom();
+
+        int count = getLineItemCount();
+        for (int i = 0; i < count; i++) {
+            if (isLineItemVisible(i)) {
+                height += getLineItemHeight(i);
+            }
+        }
+
+        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
+                resolveSize(height, heightMeasureSpec));
+    }
+
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         // Subtract padding from the borders ==> x1 variables
@@ -121,38 +184,25 @@
         final int r2;
         if (mDelete != null) {
             r2 = r1 - mDelete.getMeasuredWidth();
+            // Vertically center the delete button in the first line item
+            int height = mDelete.getMeasuredHeight();
+            int top = t1 + (mMinLineItemHeight - height) / 2;
             mDelete.layout(
-                    r2, b1 - mDelete.getMeasuredHeight(),
-                    r1, b1);
+                    r2, top,
+                    r1, top + height);
         } else {
             r2 = r1;
         }
 
         if (mLabel != null) {
+            int baseline = getBaseline(0);
+            int y = t1 + baseline - mLabel.getBaseline();
             mLabel.layout(
-                    r2 - mLabel.getMeasuredWidth(), t1,
-                    r2, t1 + mLabel.getMeasuredHeight());
+                    r2 - mLabel.getMeasuredWidth(), y,
+                    r2, y + mLabel.getMeasuredHeight());
         }
-
     }
 
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        measureChildren(widthMeasureSpec, heightMeasureSpec);
-
-        final int padding = getPaddingTop() + getPaddingBottom();
-        final int deleteHeight = mDelete != null ? mDelete.getMeasuredHeight() : 0;
-        final int labelHeight = mLabel != null ? mLabel.getMeasuredHeight() : 0;
-
-        final int height = padding +
-                Math.max(Math.max(deleteHeight, labelHeight), getEditorHeight());
-
-        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
-                resolveSize(height, heightMeasureSpec));
-    }
-
-    protected abstract int getEditorHeight();
-
     /**
      * Creates or removes the type/label button. Doesn't do anything if already correctly configured
      */
diff --git a/src/com/android/contacts/editor/TextFieldsEditorView.java b/src/com/android/contacts/editor/TextFieldsEditorView.java
index 7096a79..516ae1c 100644
--- a/src/com/android/contacts/editor/TextFieldsEditorView.java
+++ b/src/com/android/contacts/editor/TextFieldsEditorView.java
@@ -65,6 +65,30 @@
     }
 
     @Override
+    protected int getLineItemCount() {
+        int count = mFieldEditTexts == null ? 0 : mFieldEditTexts.length;
+        return Math.max(count, super.getLineItemCount());
+    }
+
+    @Override
+    protected boolean isLineItemVisible(int row) {
+        return mFieldEditTexts != null && mFieldEditTexts[row].getVisibility() != View.GONE;
+    }
+
+    @Override
+    public int getBaseline(int row) {
+        int baseline = super.getBaseline(row);
+        if (mFieldEditTexts != null) {
+            EditText editText = mFieldEditTexts[row];
+            // The text field will be centered vertically in the corresponding line item
+            int lineItemHeight = getLineItemHeight(row);
+            int offset = (lineItemHeight - editText.getMeasuredHeight()) / 2;
+            baseline = Math.max(baseline, offset + editText.getBaseline());
+        }
+        return baseline;
+    }
+
+    @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         super.onLayout(changed, l, t, r, b);
 
@@ -87,34 +111,46 @@
         // Layout text fields
         int y = t1;
         if (mFieldEditTexts != null) {
-            for (EditText editText : mFieldEditTexts) {
+            for (int i = 0; i < mFieldEditTexts.length; i++) {
+                int baseline = getBaseline(i);
+                EditText editText = mFieldEditTexts[i];
                 if (editText.getVisibility() != View.GONE) {
                     int height = editText.getMeasuredHeight();
+                    int top = t1 + y + baseline - editText.getBaseline();
                     editText.layout(
-                            l1, t1 + y,
-                            r2, t1 + y + height);
-                    y += height;
+                            l1, top,
+                            r2, top + height);
+                    y += getLineItemHeight(i);
                 }
             }
         }
     }
 
     @Override
-    protected int getEditorHeight() {
-        int result = 0;
-        // summarize the EditText heights
+    protected int getLineItemHeight(int row) {
+        int fieldHeight = 0;
+        int buttonHeight = 0;
+
+        boolean lastLineItem = true;
         if (mFieldEditTexts != null) {
-            for (EditText editText : mFieldEditTexts) {
-                if (editText.getVisibility() != View.GONE) {
-                    result += editText.getMeasuredHeight();
-                }
-            }
+            fieldHeight = mFieldEditTexts[row].getMeasuredHeight();
+            lastLineItem = (row == mFieldEditTexts.length - 1);
         }
-        // Ensure there is enough space for the minus and more/less button
-        final int deleteHeight = (getDelete() != null) ? getDelete().getMeasuredHeight() : 0;
-        final int moreOrLessHeight = mMoreOrLess != null ? mMoreOrLess.getMeasuredHeight() : 0;
-        result = Math.max(deleteHeight + moreOrLessHeight, result);
-        return result;
+
+        // Ensure there is enough space for the more/less button
+        if (row == 0) {
+            final int moreOrLessHeight = mMoreOrLess != null ? mMoreOrLess.getMeasuredHeight() : 0;
+            buttonHeight += moreOrLessHeight;
+        }
+
+        // Ensure there is enough space for the minus button
+        if (lastLineItem) {
+            View deleteButton = getDelete();
+            final int deleteHeight = (deleteButton != null) ? deleteButton.getMeasuredHeight() : 0;
+            buttonHeight += deleteHeight;
+        }
+
+        return Math.max(Math.max(buttonHeight, fieldHeight), super.getLineItemHeight(row));
     }
 
     @Override
diff --git a/src/com/android/contacts/util/ThemeUtils.java b/src/com/android/contacts/util/ThemeUtils.java
index f2d84dd..276de29 100644
--- a/src/com/android/contacts/util/ThemeUtils.java
+++ b/src/com/android/contacts/util/ThemeUtils.java
@@ -8,7 +8,7 @@
  */
 public class ThemeUtils {
     /**
-     * Resolves the given attribute id of the theme to a ressource id
+     * Resolves the given attribute id of the theme to a resource id
      */
     public static int getAttribute(Theme theme, int attrId) {
         final TypedValue outValue = new TypedValue();
@@ -17,7 +17,7 @@
     }
 
     /**
-     * Returns the ressource id of the background used for buttons to show pressed and focused state
+     * Returns the resource id of the background used for buttons to show pressed and focused state
      */
     public static int getSelectableItemBackground(Theme theme) {
         return getAttribute(theme, android.R.attr.selectableItemBackground);