Merge "Ellipsize texts in contacts list correctly"
diff --git a/res/layout/contact_tile_frequent_phone.xml b/res/layout/contact_tile_frequent_phone.xml
index d3f64e4..1cee5c6 100644
--- a/res/layout/contact_tile_frequent_phone.xml
+++ b/res/layout/contact_tile_frequent_phone.xml
@@ -67,7 +67,7 @@
                 android:id="@+id/contact_tile_phone_number"
                 android:layout_width="0dip"
                 android:layout_height="wrap_content"
-                android:layout_weight="5"
+                android:layout_weight="?attr/list_item_data_width_weight"
                 android:textSize="14sp"
                 android:textColor="@color/dialtacts_secondary_text_color"
                 android:layout_marginLeft="8dip"
@@ -78,7 +78,7 @@
                 android:id="@+id/contact_tile_phone_type"
                 android:layout_width="0dip"
                 android:layout_height="wrap_content"
-                android:layout_weight="3"
+                android:layout_weight="?attr/list_item_label_width_weight"
                 android:textSize="12sp"
                 android:ellipsize="end"
                 android:singleLine="true"
diff --git a/res/values-sw580dp/styles.xml b/res/values-sw580dp/styles.xml
index ee65159..3551378 100644
--- a/res/values-sw580dp/styles.xml
+++ b/res/values-sw580dp/styles.xml
@@ -41,8 +41,10 @@
         <item name="list_item_header_text_size">14sp</item>
         <item name="list_item_header_text_color">@color/people_app_theme_color</item>
         <item name="list_item_header_height">26dip</item>
-        <item name="list_item_header_underline_color">@color/people_app_theme_color</item>
         <item name="list_item_header_underline_height">1dip</item>
+        <item name="list_item_header_underline_color">@color/people_app_theme_color</item>
+        <item name="list_item_data_width_weight">5</item>
+        <item name="list_item_label_width_weight">3</item>
         <item name="list_item_contacts_count_text_color">@color/contact_count_text_color</item>
         <item name="list_item_contacts_count_text_size">12sp</item>
         <item name="contact_browser_list_padding_left">0dip</item>
@@ -72,8 +74,10 @@
         <item name="list_item_header_text_size">14sp</item>
         <item name="list_item_header_text_color">@color/people_app_theme_color</item>
         <item name="list_item_header_height">24dip</item>
-        <item name="list_item_header_underline_color">@color/people_app_theme_color</item>
         <item name="list_item_header_underline_height">1dip</item>
+        <item name="list_item_header_underline_color">@color/people_app_theme_color</item>
+        <item name="list_item_data_width_weight">5</item>
+        <item name="list_item_label_width_weight">3</item>
         <item name="list_item_contacts_count_text_color">@color/contact_count_text_color</item>
         <item name="list_item_contacts_count_text_size">12sp</item>
         <item name="contact_browser_list_padding_left">16dip</item>
diff --git a/res/values/attrs.xml b/res/values/attrs.xml
index a4f1b07..b9a534c 100644
--- a/res/values/attrs.xml
+++ b/res/values/attrs.xml
@@ -100,6 +100,8 @@
         <attr name="list_item_contacts_count_text_color" format="color" />
         <attr name="list_item_text_indent" format="dimension" />
         <attr name="list_item_contacts_count_text_size" format="dimension" />
+        <attr name="list_item_data_width_weight" format="integer" />
+        <attr name="list_item_label_width_weight" format="integer" />
     </declare-styleable>
 
     <declare-styleable name="CallLog">
diff --git a/res/values/styles.xml b/res/values/styles.xml
index db2e0d4..62a06f6 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -44,6 +44,8 @@
         <item name="list_item_header_height">24dip</item>
         <item name="list_item_header_underline_height">1dip</item>
         <item name="list_item_header_underline_color">@color/people_app_theme_color</item>
+        <item name="list_item_data_width_weight">5</item>
+        <item name="list_item_label_width_weight">3</item>
         <item name="contact_browser_list_padding_left">16dip</item>
         <item name="contact_browser_list_padding_right">0dip</item>
         <item name="contact_browser_background">@android:color/transparent</item>
@@ -148,6 +150,8 @@
         <item name="list_item_header_height">26dip</item>
         <item name="list_item_header_underline_height">1dip</item>
         <item name="list_item_header_underline_color">@color/people_app_theme_color</item>
+        <item name="list_item_data_width_weight">5</item>
+        <item name="list_item_label_width_weight">3</item>
         <item name="list_item_contacts_count_text_color">@color/contact_count_text_color</item>
         <item name="list_item_header_text_indent">8dip</item>
         <item name="contact_browser_list_padding_left">16dip</item>
diff --git a/src/com/android/contacts/list/ContactListItemView.java b/src/com/android/contacts/list/ContactListItemView.java
index 49957dc..18c4d29 100644
--- a/src/com/android/contacts/list/ContactListItemView.java
+++ b/src/com/android/contacts/list/ContactListItemView.java
@@ -89,11 +89,14 @@
     private final int mTextIndent;
     private Drawable mActivatedBackgroundDrawable;
 
-    // In the future we may need to merge these local padding to View's mPaddingXXX
-    private final int mExtraPaddingTop;
-    private final int mExtraPaddingBottom;
-    private int mExtraPaddingLeft;
-    private int mExtraPaddingRight;
+    /**
+     * Used with {@link #mLabelView}, specifying the width ratio between label and data.
+     */
+    private final int mLabelViewWidthWeight;
+    /**
+     * Used with {@link #mDataView}, specifying the width ratio between label and data.
+     */
+    private final int mDataViewWidthWeight;
 
     // Will be used with adjustListItemSelectionBounds().
     private int mSelectionBoundsMarginLeft;
@@ -230,14 +233,7 @@
                 R.styleable.ContactListItemView_list_item_divider);
         mVerticalDividerMargin = a.getDimensionPixelOffset(
                 R.styleable.ContactListItemView_list_item_vertical_divider_margin, 0);
-        mExtraPaddingTop = a.getDimensionPixelOffset(
-                R.styleable.ContactListItemView_list_item_padding_top, 0);
-        mExtraPaddingBottom = a.getDimensionPixelOffset(
-                R.styleable.ContactListItemView_list_item_padding_bottom, 0);
-        mExtraPaddingLeft = a.getDimensionPixelOffset(
-                R.styleable.ContactListItemView_list_item_padding_left, 0);
-        mExtraPaddingRight = a.getDimensionPixelOffset(
-                R.styleable.ContactListItemView_list_item_padding_right, 0);
+
         mGapBetweenImageAndText = a.getDimensionPixelOffset(
                 R.styleable.ContactListItemView_list_item_gap_between_image_and_text, 0);
         mGapBetweenLabelAndData = a.getDimensionPixelOffset(
@@ -268,6 +264,20 @@
                 R.styleable.ContactListItemView_list_item_contacts_count_text_size, 12);
         mContactsCountTextColor = a.getColor(
                 R.styleable.ContactListItemView_list_item_contacts_count_text_color, Color.BLACK);
+        mDataViewWidthWeight = a.getInteger(
+                R.styleable.ContactListItemView_list_item_data_width_weight, 5);
+        mLabelViewWidthWeight = a.getInteger(
+                R.styleable.ContactListItemView_list_item_label_width_weight, 3);
+
+        setPadding(
+                a.getDimensionPixelOffset(
+                        R.styleable.ContactListItemView_list_item_padding_left, 0),
+                a.getDimensionPixelOffset(
+                        R.styleable.ContactListItemView_list_item_padding_top, 0),
+                a.getDimensionPixelOffset(
+                        R.styleable.ContactListItemView_list_item_padding_right, 0),
+                a.getDimensionPixelOffset(
+                        R.styleable.ContactListItemView_list_item_padding_bottom, 0));
 
         mPrefixHighligher = new PrefixHighlighter(
                 a.getColor(R.styleable.ContactListItemView_list_item_prefix_highlight_color,
@@ -306,9 +316,13 @@
     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
         // We will match parent's width and wrap content vertically, but make sure
         // height is no less than listPreferredItemHeight.
-        int width = resolveSize(0, widthMeasureSpec);
-        int height = 0;
-        int preferredHeight = mPreferredHeight;
+        final int specWidth = resolveSize(0, widthMeasureSpec);
+        final int preferredHeight;
+        if (mHorizontalDividerVisible) {
+            preferredHeight = mPreferredHeight + mHorizontalDividerHeight;
+        } else {
+            preferredHeight = mPreferredHeight;
+        }
 
         mNameTextViewHeight = 0;
         mPhoneticNameTextViewHeight = 0;
@@ -318,51 +332,76 @@
         mSnippetTextViewHeight = 0;
         mStatusTextViewHeight = 0;
 
-        // TODO: measure(0, 0) is *wrong*. At least, we should use correct width for each TextView.
-        //
-        // Reason: TextView applies ellipsis effect in this phase, while measure(0, 0) have those
-        // views prepare the effect based on "unlimited width", which makes ellipsis setting
-        // meaningless. We should pass a widthMeasureSpec with appropriate width setting.
-        // See issue 5439903.
-
         ensurePhotoViewSize();
 
-        // Go over all visible text views and add their heights to get the total height
+        // Width each TextView is able to use.
+        final int effectiveWidth;
+        // All the other Views will honor the photo, so available width for them may be shrunk.
+        if (mPhotoViewWidth > 0 || mKeepHorizontalPaddingForPhotoView) {
+            effectiveWidth = specWidth - getPaddingLeft() - getPaddingRight()
+                    - (mPhotoViewWidth + mGapBetweenImageAndText);
+        } else {
+            effectiveWidth = specWidth - getPaddingLeft() - getPaddingRight();
+        }
+
+        // Go over all visible text views and measure actual width of each of them.
+        // Also calculate their heights to get the total height for this entire view.
+
         if (isVisible(mNameTextView)) {
-            mNameTextView.measure(0, 0);
+            mNameTextView.measure(
+                    MeasureSpec.makeMeasureSpec(effectiveWidth, MeasureSpec.AT_MOST),
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
             mNameTextViewHeight = mNameTextView.getMeasuredHeight();
         }
 
         if (isVisible(mPhoneticNameTextView)) {
-            mPhoneticNameTextView.measure(0, 0);
+            mPhoneticNameTextView.measure(
+                    MeasureSpec.makeMeasureSpec(effectiveWidth, MeasureSpec.AT_MOST),
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
             mPhoneticNameTextViewHeight = mPhoneticNameTextView.getMeasuredHeight();
         }
 
+        // If both data (phone number/email address) and label (type like "MOBILE") are quite long,
+        // we should ellipsize both using appropriate ratio.
+        final int dataWidth;
+        final int labelWidth;
         if (isVisible(mDataView)) {
-            mDataView.measure(0, 0);
+            if (isVisible(mLabelView)) {
+                final int totalWidth = effectiveWidth - mGapBetweenLabelAndData;
+                dataWidth = ((totalWidth * mDataViewWidthWeight)
+                        / (mDataViewWidthWeight + mLabelViewWidthWeight));
+                labelWidth = ((totalWidth * mLabelViewWidthWeight) /
+                        (mDataViewWidthWeight + mLabelViewWidthWeight));
+            } else {
+                dataWidth = effectiveWidth;
+                labelWidth = 0;
+            }
+        } else {
+            dataWidth = 0;
+            if (isVisible(mLabelView)) {
+                labelWidth = effectiveWidth;
+            } else {
+                labelWidth = 0;
+            }
+        }
+
+        if (isVisible(mDataView)) {
+            mDataView.measure(MeasureSpec.makeMeasureSpec(dataWidth, MeasureSpec.AT_MOST),
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
             mDataViewHeight = mDataView.getMeasuredHeight();
         }
 
         if (isVisible(mLabelView)) {
-            if (mPhotoPosition == PhotoPosition.LEFT) {
-                // Manually calculate the width now and see if ellipsis becomes effective or not.
-                // See also issue 5438757 and 5439903.
-                final int labelViewWidth = width - mExtraPaddingLeft - mExtraPaddingRight
-                        - (mPhotoViewWidth + mGapBetweenImageAndText)
-                        - mDataView.getMeasuredWidth()
-                        - mGapBetweenLabelAndData;
-                final int labelViewWidthMeasureSpec = MeasureSpec.makeMeasureSpec(
-                        labelViewWidth, MeasureSpec.AT_MOST);
-                mLabelView.measure(labelViewWidthMeasureSpec, 0);
-            } else {
-                mLabelView.measure(0, 0);
-            }
+            mLabelView.measure(MeasureSpec.makeMeasureSpec(labelWidth, MeasureSpec.AT_MOST),
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
             mLabelTextViewHeight = mLabelView.getMeasuredHeight();
         }
         mLabelAndDataViewMaxHeight = Math.max(mLabelTextViewHeight, mDataViewHeight);
 
         if (isVisible(mSnippetView)) {
-            mSnippetView.measure(0, 0);
+            mSnippetView.measure(
+                    MeasureSpec.makeMeasureSpec(effectiveWidth, MeasureSpec.AT_MOST),
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
             mSnippetTextViewHeight = mSnippetView.getMeasuredHeight();
         }
 
@@ -373,27 +412,36 @@
         }
 
         if (isVisible(mStatusView)) {
-            mStatusView.measure(0, 0);
-            mStatusTextViewHeight = Math.max(mStatusTextViewHeight,
-                    mStatusView.getMeasuredHeight());
+            // Presence and status are in a same row, so status will be affected by icon size.
+            final int statusWidth;
+            if (isVisible(mPresenceIcon)) {
+                statusWidth = (effectiveWidth - mPresenceIcon.getMeasuredWidth()
+                        - mPresenceIconMargin);
+            } else {
+                statusWidth = effectiveWidth;
+            }
+            mStatusView.measure(MeasureSpec.makeMeasureSpec(statusWidth, MeasureSpec.AT_MOST),
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
+            mStatusTextViewHeight =
+                    Math.max(mStatusTextViewHeight, mStatusView.getMeasuredHeight());
         }
 
-        // Calculate height including padding
-        height += mNameTextViewHeight + mPhoneticNameTextViewHeight + mLabelAndDataViewMaxHeight +
-                mSnippetTextViewHeight + mStatusTextViewHeight +
-                mExtraPaddingTop + mExtraPaddingBottom;
+        // Calculate height including padding.
+        int height = (mNameTextViewHeight + mPhoneticNameTextViewHeight +
+                mLabelAndDataViewMaxHeight +
+                mSnippetTextViewHeight + mStatusTextViewHeight);
 
         if (isVisible(mCallButton)) {
-            mCallButton.measure(0, 0);
+            mCallButton.measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                    MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED));
         }
 
         // Make sure the height is at least as high as the photo
-        height = Math.max(height, mPhotoViewHeight + mExtraPaddingBottom + mExtraPaddingTop);
+        height = Math.max(height, mPhotoViewHeight + getPaddingBottom() + getPaddingTop());
 
         // Add horizontal divider height
         if (mHorizontalDividerVisible) {
             height += mHorizontalDividerHeight;
-            preferredHeight += mHorizontalDividerHeight;
         }
 
         // Make sure height is at least the preferred height
@@ -402,11 +450,11 @@
         // Add the height of the header if visible
         if (mHeaderVisible) {
             mHeaderTextView.measure(
-                    MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
+                    MeasureSpec.makeMeasureSpec(specWidth, MeasureSpec.EXACTLY),
                     MeasureSpec.makeMeasureSpec(mHeaderBackgroundHeight, MeasureSpec.EXACTLY));
             if (mCountView != null) {
                 mCountView.measure(
-                        MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
+                        MeasureSpec.makeMeasureSpec(specWidth, MeasureSpec.AT_MOST),
                         MeasureSpec.makeMeasureSpec(mHeaderBackgroundHeight, MeasureSpec.EXACTLY));
             }
             mHeaderBackgroundHeight = Math.max(mHeaderBackgroundHeight,
@@ -414,7 +462,7 @@
             height += (mHeaderBackgroundHeight + mHeaderUnderlineHeight);
         }
 
-        setMeasuredDimension(width, height);
+        setMeasuredDimension(specWidth, height);
     }
 
     @Override
@@ -425,8 +473,8 @@
         // Determine the vertical bounds by laying out the header first.
         int topBound = 0;
         int bottomBound = height;
-        int leftBound = mExtraPaddingLeft;
-        int rightBound = width - mExtraPaddingRight;
+        int leftBound = getPaddingLeft();
+        int rightBound = width - getPaddingRight();
 
         // Put the header in the top of the contact view (Text + underline view)
         if (mHeaderVisible) {
@@ -435,7 +483,7 @@
                     rightBound,
                     mHeaderBackgroundHeight);
             if (mCountView != null) {
-                mCountView.layout(width - mExtraPaddingRight - mCountView.getMeasuredWidth(),
+                mCountView.layout(rightBound - mCountView.getMeasuredWidth(),
                         0,
                         rightBound,
                         mHeaderBackgroundHeight);
@@ -463,10 +511,6 @@
             mActivatedBackgroundDrawable.setBounds(mBoundsWithoutHeader);
         }
 
-        // Set the top/bottom padding
-        topBound += mExtraPaddingTop;
-        bottomBound -= mExtraPaddingBottom;
-
         final View photoView = mQuickContact != null ? mQuickContact : mPhotoView;
         if (mPhotoPosition == PhotoPosition.LEFT) {
             // Photo is the left most view. All the other Views should on the right of the photo.
@@ -947,7 +991,6 @@
             mLabelView.setTextAppearance(mContext, android.R.style.TextAppearance_Small);
             if (mPhotoPosition == PhotoPosition.LEFT) {
                 mLabelView.setTextSize(TypedValue.COMPLEX_UNIT_SP, mCountViewTextSize);
-                mLabelView.setEllipsize(TruncateAt.MIDDLE);
                 mLabelView.setAllCaps(true);
             } else {
                 mLabelView.setTypeface(mLabelView.getTypeface(), Typeface.BOLD);
@@ -1103,7 +1146,9 @@
     }
 
     private TruncateAt getTextEllipsis() {
-        return mActivatedStateSupported ? TruncateAt.START : TruncateAt.MARQUEE;
+        // Note: If we want to choose MARQUEE here, we may need to manually trigger TextView's
+        // startStopMarquee(), which is unfortunately *private*. See also issue 5465510.
+        return TruncateAt.MIDDLE;
     }
 
     public void showDisplayName(Cursor cursor, int nameColumnIndex, int alternativeNameColumnIndex,
@@ -1262,29 +1307,6 @@
     }
 
     /**
-     * Sets custom padding inside this object. Do not use this method without any strong reason.
-     *
-     * Detail: we cannot simply override {@link #setPadding(int, int, int, int)}. {@link View}
-     * does *not* know this view's local padding but has completely different ones.
-     * See View#mPaddingLeft and View#mPaddingRight. View also has View#mUserPaddingLeft, and
-     * View#mUserPaddingRight in addition to View#mPaddingLeft and View#mPaddingRight, to handle
-     * {@link View#setPadding(int, int, int, int)} correctly. If setPadding() is overridden to
-     * reset our {@link #mExtraPaddingLeft} and {@link #mExtraPaddingRight} carelessly, the whole
-     * View layout gets confused.
-     *
-     * To simplify our implementation, this method just modify the local two padding without
-     * confusing its parent.
-     *
-     * If we want to fix this multiple padding issue correctly, we should merge local padding
-     * in this class into View's ones. Also we should remove "list_item_padding_left" and
-     * "list_item_padding_right" attributes, using "android:paddingLeft" and "android:paddingRight".
-     */
-    public void setExtraPadding(int left, int right) {
-        mExtraPaddingLeft = left;
-        mExtraPaddingRight = right;
-    }
-
-    /**
      * Specifies left and right margin for selection bounds. See also
      * {@link #adjustListItemSelectionBounds(Rect)}.
      */
diff --git a/src/com/android/contacts/list/PhoneFavoriteMergedAdapter.java b/src/com/android/contacts/list/PhoneFavoriteMergedAdapter.java
index 25a158b..f817b4c 100644
--- a/src/com/android/contacts/list/PhoneFavoriteMergedAdapter.java
+++ b/src/com/android/contacts/list/PhoneFavoriteMergedAdapter.java
@@ -163,9 +163,8 @@
             final int localPosition = position - contactTileAdapterCount - 1;
             final ContactListItemView itemView = (ContactListItemView)
                     mContactEntryListAdapter.getView(localPosition, convertView, null);
-            // We cannot simply use setPadding() because of ContactListItemView's restriction.
-            // See comments for setExtraPaddingPadding().
-            itemView.setExtraPadding(mItemPaddingLeft, mItemPaddingRight);
+            itemView.setPadding(mItemPaddingLeft, itemView.getPaddingTop(),
+                    mItemPaddingRight, itemView.getPaddingBottom());
             itemView.setSelectionBoundsHorizontalMargin(mItemPaddingLeft, mItemPaddingRight);
             return itemView;
         }