Merge "Drag and drop into folders."
diff --git a/res/layout/conversation_message_details_header.xml b/res/layout/conversation_message_details_header.xml
index 4a7f5b4..0500e4b 100644
--- a/res/layout/conversation_message_details_header.xml
+++ b/res/layout/conversation_message_details_header.xml
@@ -15,32 +15,25 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-    <LinearLayout
-        android:id="@+id/details_collapsed_content"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/message_details_header_collapsed_height"
-        android:background="?android:attr/selectableItemBackground"
-        android:paddingLeft="@dimen/message_details_header_padding_left"
-        android:paddingRight="@dimen/message_details_header_padding_right"
-        android:gravity="center_vertical">
-        <TextView
-            android:id="@+id/recipients_summary"
-            android:layout_weight="1"
-            android:layout_marginRight="16dip"
-            android:singleLine="true"
-            android:bufferType="spannable"
-            style="@style/MessageHeaderSmallStyle" />
-        <TextView
-            android:id="@+id/date_summary"
-            android:lines="1"
-            style="@style/MessageHeaderDateTextStyle" />
-        <ImageView
-            style="@style/MessageHeaderExpanderMinimizedStyle" />
-    </LinearLayout>
-    <View
-        android:id="@+id/details_bottom_border"
-        android:layout_width="match_parent"
-        android:layout_height="@dimen/message_details_header_bottom_border_height"
-        android:background="@color/conv_subject_border" />
-</merge>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/details_collapsed_content"
+    android:layout_width="match_parent"
+    android:layout_height="@dimen/message_details_header_collapsed_height"
+    android:background="?android:attr/selectableItemBackground"
+    android:paddingLeft="@dimen/message_details_header_padding_left"
+    android:paddingRight="@dimen/message_details_header_padding_right"
+    android:gravity="center_vertical">
+    <TextView
+        android:id="@+id/recipients_summary"
+        android:layout_weight="1"
+        android:layout_marginRight="16dip"
+        android:singleLine="true"
+        android:bufferType="spannable"
+        style="@style/MessageHeaderSmallStyle" />
+    <TextView
+        android:id="@+id/date_summary"
+        android:lines="1"
+        style="@style/MessageHeaderDateTextStyle" />
+    <ImageView
+        style="@style/MessageHeaderExpanderMinimizedStyle" />
+</LinearLayout>
diff --git a/res/layout/conversation_message_header.xml b/res/layout/conversation_message_header.xml
index dcb370b..d14df48 100644
--- a/res/layout/conversation_message_header.xml
+++ b/res/layout/conversation_message_header.xml
@@ -27,4 +27,11 @@
     <include layout="@layout/conversation_message_upper_header"
         android:id="@+id/upper_header" />
 
+    <View
+        android:id="@+id/details_bottom_border"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/message_details_header_bottom_border_height"
+        android:visibility="gone"
+        android:background="@color/conv_subject_border" />
+
 </com.android.mail.browse.MessageHeaderView>
diff --git a/src/com/android/mail/browse/ConversationContainer.java b/src/com/android/mail/browse/ConversationContainer.java
index 1aeb4c6..e563bdd 100644
--- a/src/com/android/mail/browse/ConversationContainer.java
+++ b/src/com/android/mail/browse/ConversationContainer.java
@@ -17,8 +17,6 @@
 
 package com.android.mail.browse;
 
-import com.google.common.collect.Sets;
-
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.MotionEvent;
@@ -35,6 +33,7 @@
 import com.android.mail.browse.ScrollNotifier.ScrollListener;
 import com.android.mail.utils.DequeMap;
 import com.android.mail.utils.LogUtils;
+import com.google.common.collect.Sets;
 
 import java.util.Set;
 
@@ -338,13 +337,22 @@
     }
 
     /**
+     * Executes a measure pass over the specified child overlay view and returns the measured
+     * height. The measurement uses whatever the current container's width measure spec is.
+     * This method ignores view visibility and returns the height that the view would be if visible.
+     *
+     * @param overlayView an overlay view to measure. does not actually have to be attached yet.
+     * @return height that the view would be if it was visible
+     */
+    public int measureOverlay(View overlayView) {
+        measureOverlayView(overlayView);
+        return overlayView.getMeasuredHeight();
+    }
+
+    /**
      * Copied/stolen from {@link ListView}.
      */
-    private void measureItem(View child) {
-        if (child.getVisibility() == GONE) {
-            return;
-        }
-
+    private void measureOverlayView(View child) {
         ViewGroup.LayoutParams p = child.getLayoutParams();
         if (p == null) {
             p = new ViewGroup.LayoutParams(
@@ -429,14 +437,8 @@
         }
         mWidthMeasureSpec = widthMeasureSpec;
 
-        // Need to measure children in case this layout pass was triggered by a child layout change.
-        // TODO: restrict child measurement to just that case.
-        for (int i = 0, overlayCount = getOverlayCount(); i < overlayCount; i++) {
-            final View overlayView = getOverlayAt(i);
-            if (overlayView.getVisibility() != GONE) {
-                measureItem(overlayView);
-            }
-        }
+        // onLayout will re-measure and re-position overlays for the new container size, but the
+        // spacer offsets would still need to be updated to have them draw at their new locations.
     }
 
     @Override
@@ -447,6 +449,12 @@
         positionOverlays(0, mOffsetY);
     }
 
+    @Override
+    public void requestLayout() {
+        // Suppress layouts requested by children. Overlays don't push on each other, and WebView
+        // doesn't change its layout.
+    }
+
     private int getOverlayBottom(int spacerIndex) {
         // TODO: round or truncate?
         return (int) (mOverlayBottoms[spacerIndex] * mScale);
@@ -454,24 +462,34 @@
 
     private void positionOverlay(int adapterIndex, int overlayTopY, int overlayBottomY) {
         View overlayView = findExistingOverlayView(adapterIndex);
-        final int itemType = mOverlayAdapter.getItemViewType(adapterIndex);
+        final ConversationItem item = mOverlayAdapter.getItem(adapterIndex);
+
         // is the overlay visible and does it have non-zero height?
         if (overlayTopY != overlayBottomY && overlayBottomY > mOffsetY
                 && overlayTopY < mOffsetY + getHeight()) {
             // show and/or move overlay
             if (overlayView == null) {
                 overlayView = addOverlayView(adapterIndex);
-                measureItem(overlayView);
-                traceLayout("show overlay %d", adapterIndex);
+                measureOverlayView(overlayView);
+                item.markMeasurementValid();
+                traceLayout("show/measure overlay %d", adapterIndex);
             } else {
                 traceLayout("move overlay %d", adapterIndex);
+                if (!item.isMeasurementValid()) {
+                    measureOverlayView(overlayView);
+                    item.markMeasurementValid();
+                    traceLayout("and (re)measure overlay %d, old/new heights=%d/%d", adapterIndex,
+                            overlayView.getHeight(), overlayView.getMeasuredHeight());
+                }
             }
+            traceLayout("laying out overlay %d with h=%d", adapterIndex,
+                    overlayView.getMeasuredHeight());
             layoutOverlay(overlayView, overlayTopY);
         } else {
             // hide overlay
             if (overlayView != null) {
                 traceLayout("hide overlay %d", adapterIndex);
-                onOverlayScrolledOff(overlayView, itemType, overlayTopY, overlayBottomY);
+                onOverlayScrolledOff(overlayView, item.getType(), overlayTopY, overlayBottomY);
             } else {
                 traceLayout("ignore non-visible overlay %d", adapterIndex);
             }
@@ -485,10 +503,6 @@
     // layout an existing view
     // need its top offset into the conversation, its height, and the scroll offset
     private void layoutOverlay(View child, int childTop, int childBottom) {
-        if (child.getVisibility() == GONE) {
-            return;
-        }
-
         final int top = childTop - mOffsetY;
         final int bottom = childBottom - mOffsetY;
         child.layout(0, top, child.getMeasuredWidth(), bottom);
diff --git a/src/com/android/mail/browse/ConversationViewAdapter.java b/src/com/android/mail/browse/ConversationViewAdapter.java
index 282e39e..2d09d22 100644
--- a/src/com/android/mail/browse/ConversationViewAdapter.java
+++ b/src/com/android/mail/browse/ConversationViewAdapter.java
@@ -35,7 +35,6 @@
 import com.android.mail.providers.Message;
 import com.android.mail.providers.UIProvider;
 import com.android.mail.utils.LogUtils;
-import com.android.mail.utils.Utils;
 import com.google.common.collect.Lists;
 
 import java.util.List;
@@ -73,6 +72,7 @@
 
     public static abstract class ConversationItem {
         private int mHeight;  // in px
+        private boolean mNeedsMeasure;
 
         /**
          * @see Adapter#getItemViewType(int)
@@ -96,15 +96,6 @@
         public abstract boolean isContiguous();
 
         /**
-         * Measure the expected visible height of the overlay view. Even if the view is initially
-         * GONE, this method must return whatever height the view is going to be when it is later
-         * made VISIBLE.
-         */
-        public int measureHeight(View v, ViewGroup parent) {
-            return Utils.measureViewHeight(v, parent);
-        }
-
-        /**
          * This method's behavior is critical and requires some 'splainin.
          * <p>
          * Subclasses that return a zero-size height to the {@link ConversationContainer} will
@@ -120,7 +111,22 @@
 
         public void setHeight(int h) {
             LogUtils.i(LOG_TAG, "IN setHeight=%dpx of overlay item: %s", h, this);
-            mHeight = h;
+            if (mHeight != h) {
+                mHeight = h;
+                mNeedsMeasure = true;
+            }
+        }
+
+        public boolean isMeasurementValid() {
+            return !mNeedsMeasure;
+        }
+
+        public void markMeasurementValid() {
+            mNeedsMeasure = false;
+        }
+
+        public void invalidateMeasurement() {
+            mNeedsMeasure = true;
         }
     }
 
diff --git a/src/com/android/mail/browse/ConversationViewHeader.java b/src/com/android/mail/browse/ConversationViewHeader.java
index 1ff04ba..1fdf732 100644
--- a/src/com/android/mail/browse/ConversationViewHeader.java
+++ b/src/com/android/mail/browse/ConversationViewHeader.java
@@ -169,11 +169,6 @@
         }
     }
 
-    public int getPremeasuredHeight() {
-        ViewGroup parent = (ViewGroup) getParent();
-        return Utils.measureViewHeight(this, parent);
-    }
-
     @Override
     public void onClick(View v) {
         if (R.id.folders == v.getId()) {
diff --git a/src/com/android/mail/browse/MessageHeaderView.java b/src/com/android/mail/browse/MessageHeaderView.java
index 8187942..d9c3259 100644
--- a/src/com/android/mail/browse/MessageHeaderView.java
+++ b/src/com/android/mail/browse/MessageHeaderView.java
@@ -83,6 +83,7 @@
 
     private MessageHeaderViewCallbacks mCallbacks;
 
+    private ViewGroup mUpperHeaderView;
     private TextView mSenderNameView;
     private TextView mSenderEmailView;
     private QuickContactBadge mPhotoView;
@@ -185,6 +186,7 @@
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
+        mUpperHeaderView = (ViewGroup) findViewById(R.id.upper_header);
         mSenderNameView = (TextView) findViewById(R.id.sender_name);
         mSenderEmailView = (TextView) findViewById(R.id.sender_email);
         mPhotoView = (QuickContactBadge) findViewById(R.id.photo);
@@ -196,6 +198,8 @@
         mTitleContainerCollapsedMarginRight = ((MarginLayoutParams) mTitleContainerView
                 .getLayoutParams()).rightMargin;
 
+        mBottomBorderView = findViewById(R.id.details_bottom_border);
+
         setExpanded(true);
 
         registerMessageClickTargets(R.id.reply, R.id.reply_all, R.id.forward, R.id.star,
@@ -939,20 +943,9 @@
      */
     private void showCollapsedDetails() {
         if (mCollapsedDetailsView == null) {
-            // Collapsed details is a merge layout that also contains the bottom
-            // border. The
-            // assumption is that collapsed is inflated before expanded. If we
-            // ever change this
-            // so either may be inflated first, the bottom border should be
-            // moved out into a
-            // separate layout and inflated alongside either collapsed or
-            // expanded, whichever is
-            // first.
-            mInflater.inflate(R.layout.conversation_message_details_header, this);
-
-            mBottomBorderView = findViewById(R.id.details_bottom_border);
-            mCollapsedDetailsView = (ViewGroup) findViewById(R.id.details_collapsed_content);
-
+            mCollapsedDetailsView = (ViewGroup) mInflater.inflate(
+                    R.layout.conversation_message_details_header, this, false);
+            addView(mCollapsedDetailsView, indexOfChild(mUpperHeaderView) + 1);
             mCollapsedDetailsView.setOnClickListener(this);
         }
         if (!mCollapsedDetailsValid) {
@@ -976,14 +969,7 @@
         if (mExpandedDetailsView == null) {
             View v = mInflater.inflate(R.layout.conversation_message_details_header_expanded,
                     this, false);
-
-            // Insert expanded details into the parent linear layout immediately
-            // after the
-            // previously inflated collapsed details view, and above any other
-            // optional views
-            // like 'show pictures' or attachments.
-            // we assume collapsed has been inflated by now
-            addView(v, indexOfChild(mCollapsedDetailsView) + 1);
+            addView(v, indexOfChild(mUpperHeaderView) + 1);
             v.setOnClickListener(this);
 
             mExpandedDetailsView = (ViewGroup) v;
diff --git a/src/com/android/mail/ui/ConversationViewFragment.java b/src/com/android/mail/ui/ConversationViewFragment.java
index 1f3ba50..aa2a1be 100644
--- a/src/com/android/mail/ui/ConversationViewFragment.java
+++ b/src/com/android/mail/ui/ConversationViewFragment.java
@@ -405,8 +405,9 @@
             mConversationContainer.addScrapView(type, hostView);
         }
 
-        final int heightPx = convItem.measureHeight(hostView, mConversationContainer);
+        final int heightPx = mConversationContainer.measureOverlay(hostView);
         convItem.setHeight(heightPx);
+        convItem.markMeasurementValid();
 
         return (int) (heightPx / mDensity);
     }