Merge "Try adjustments to the archive animation per danship." into jb-ub-mail
diff --git a/res/layout/cc_bcc_view.xml b/res/layout/cc_bcc_view.xml
index 5681cb1..acc957b 100644
--- a/res/layout/cc_bcc_view.xml
+++ b/res/layout/cc_bcc_view.xml
@@ -38,7 +38,8 @@
 
                 <com.android.ex.chips.RecipientEditTextView
                     android:id="@+id/cc"
-                    style="@style/RecipientEditTextViewStyle"/>
+                    style="@style/RecipientEditTextViewStyle"
+                    android:contentDescription="@string/cc"/>
         </LinearLayout>
 
         <View style="@style/RecipientComposeFieldSpacer"/>
@@ -62,7 +63,8 @@
 
             <com.android.ex.chips.RecipientEditTextView
                 android:id="@+id/bcc"
-                style="@style/RecipientEditTextViewStyle"/>
+                style="@style/RecipientEditTextViewStyle"
+                android:contentDescription="@string/bcc"/>
 
         </LinearLayout>
 
diff --git a/res/layout/compose_recipients.xml b/res/layout/compose_recipients.xml
index 0873b18..4211a0b 100644
--- a/res/layout/compose_recipients.xml
+++ b/res/layout/compose_recipients.xml
@@ -37,7 +37,8 @@
 
             <com.android.ex.chips.RecipientEditTextView
                 android:id="@+id/to"
-                style="@style/RecipientEditTextViewStyle"/>
+                style="@style/RecipientEditTextViewStyle"
+                android:contentDescription="@string/to"/>
 
         </LinearLayout>
 
diff --git a/res/layout/conversation_view.xml b/res/layout/conversation_view.xml
index 208a42e..438e2b8 100644
--- a/res/layout/conversation_view.xml
+++ b/res/layout/conversation_view.xml
@@ -36,7 +36,17 @@
         android:layout_width="match_parent"
         android:layout_height="match_parent">
 
-        <include layout="@layout/new_message_notification_bar" />
+        <include layout="@layout/conversation_message_header"
+            android:id="@+id/snap_header"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="top"
+            android:visibility="gone" />
+
+        <include layout="@layout/new_message_notification_bar"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_gravity="bottom" />
 
         <!-- TODO: scroll indicators go on top of all other layers (or the parent draws it) -->
     </FrameLayout>
diff --git a/res/layout/new_message_notification_bar.xml b/res/layout/new_message_notification_bar.xml
index d08ed42..b14f607 100644
--- a/res/layout/new_message_notification_bar.xml
+++ b/res/layout/new_message_notification_bar.xml
@@ -17,9 +17,6 @@
 -->
 <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:layout_gravity="bottom"
     android:layout_marginBottom="12dp"
     android:layout_marginLeft="@dimen/new_message_notification_margin_side"
     android:layout_marginRight="@dimen/new_message_notification_margin_side"
diff --git a/res/values-sw600dp/styles.xml b/res/values-sw600dp/styles.xml
index 0f7fce3..8c530e7 100644
--- a/res/values-sw600dp/styles.xml
+++ b/res/values-sw600dp/styles.xml
@@ -57,4 +57,7 @@
     <style name="AccountSpinnerAnchorTextPrimary" parent="@android:style/TextAppearance.Holo.Widget.ActionBar.Title">
     </style>
 
+    <style name="AccountSpinnerStyle">
+        <item name="android:layout_width">@dimen/spinner_frame_width</item>
+    </style>
 </resources>
diff --git a/res/values/styles.xml b/res/values/styles.xml
index 46a5f72..5973db3 100644
--- a/res/values/styles.xml
+++ b/res/values/styles.xml
@@ -452,6 +452,6 @@
     </style>
 
     <style name="AccountSpinnerStyle">
-        <item name="android:layout_width">@dimen/spinner_frame_width</item>
+        <item name="android:layout_width">wrap_content</item>
     </style>
 </resources>
diff --git a/src/com/android/mail/browse/ConversationContainer.java b/src/com/android/mail/browse/ConversationContainer.java
index 9ce1acc..310a67e 100644
--- a/src/com/android/mail/browse/ConversationContainer.java
+++ b/src/com/android/mail/browse/ConversationContainer.java
@@ -19,6 +19,7 @@
 
 import android.content.Context;
 import android.database.DataSetObserver;
+import android.graphics.Canvas;
 import android.util.AttributeSet;
 import android.util.SparseArray;
 import android.view.MotionEvent;
@@ -71,11 +72,25 @@
     private static final int[] TOP_LAYER_VIEW_IDS = {
         R.id.conversation_topmost_overlay
     };
-    private static final int TOP_LAYER_COUNT = TOP_LAYER_VIEW_IDS.length;
 
     private ConversationViewAdapter mOverlayAdapter;
     private int[] mOverlayBottoms;
     private ConversationWebView mWebView;
+    private MessageHeaderView mSnapHeader;
+    private View mTopMostOverlay;
+
+    /**
+     * This is a hack.
+     *
+     * <p>Without this hack enabled, very fast scrolling can sometimes cause the top-most layers
+     * to skip being drawn for a frame or two. It happens specifically when overlay views are
+     * attached or added, and WebView happens to draw (on its own) immediately afterwards.
+     *
+     * <p>The workaround is to force an additional draw of the top-most overlay. Since the problem
+     * only occurs when scrolling overlays are added, restrict the additional draw to only occur
+     * if scrolling overlays were added since the last draw.
+     */
+    private boolean mAttachedOverlaySinceLastDraw;
 
     private final List<View> mNonScrollingChildren = Lists.newArrayList();
 
@@ -146,6 +161,14 @@
     private final DataSetObserver mAdapterObserver = new AdapterObserver();
 
     /**
+     * The adapter index of the lowest overlay item that is above the top of the screen and reports
+     * {@link ConversationOverlayItem#canPushSnapHeader()}. We calculate this after a pass through
+     * {@link #positionOverlays(int, int)}.
+     *
+     */
+    private int mSnapIndex;
+
+    /**
      * Child views of this container should implement this interface to be notified when they are
      * being detached.
      *
@@ -193,6 +216,11 @@
         mWebView = (ConversationWebView) findViewById(R.id.webview);
         mWebView.addScrollListener(this);
 
+        mTopMostOverlay = findViewById(R.id.conversation_topmost_overlay);
+
+        mSnapHeader = (MessageHeaderView) findViewById(R.id.snap_header);
+        mSnapHeader.setSnappy(true);
+
         for (int id : BOTTOM_LAYER_VIEW_IDS) {
             mNonScrollingChildren.add(findViewById(id));
         }
@@ -201,6 +229,10 @@
         }
     }
 
+    public MessageHeaderView getSnapHeader() {
+        return mSnapHeader;
+    }
+
     public void setOverlayAdapter(ConversationViewAdapter a) {
         if (mOverlayAdapter != null) {
             mOverlayAdapter.unregisterDataSetObserver(mAdapterObserver);
@@ -346,6 +378,8 @@
         traceLayout("IN positionOverlays, spacerCount=%d overlayCount=%d", mOverlayBottoms.length,
                 mOverlayAdapter.getCount());
 
+        mSnapIndex = -1;
+
         int adapterIndex = mOverlayAdapter.getCount() - 1;
         int spacerIndex = mOverlayBottoms.length - 1;
         while (spacerIndex >= 0 && adapterIndex >= 0) {
@@ -380,6 +414,22 @@
 
             spacerIndex--;
         }
+
+        // render and/or re-position snap header
+        ConversationOverlayItem snapItem = null;
+        if (mSnapIndex != -1) {
+            final ConversationOverlayItem item = mOverlayAdapter.getItem(mSnapIndex);
+            if (item.canBecomeSnapHeader()) {
+                snapItem = item;
+            }
+        }
+        if (snapItem == null) {
+            mSnapHeader.setVisibility(GONE);
+            mSnapHeader.unbind();
+        } else {
+            snapItem.bindView(mSnapHeader, false /* measureOnly */);
+            mSnapHeader.setVisibility(VISIBLE);
+        }
     }
 
     /**
@@ -516,6 +566,16 @@
     }
 
     @Override
+    protected void dispatchDraw(Canvas canvas) {
+        super.dispatchDraw(canvas);
+
+        if (mAttachedOverlaySinceLastDraw) {
+            drawChild(canvas, mTopMostOverlay, getDrawingTime());
+            mAttachedOverlaySinceLastDraw = false;
+        }
+    }
+
+    @Override
     protected LayoutParams generateDefaultLayoutParams() {
         return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
     }
@@ -575,6 +635,15 @@
                 traceLayout("ignore non-visible overlay %d", adapterIndex);
             }
         }
+
+        if (overlayTopY <= mOffsetY && item.canPushSnapHeader()) {
+            if (mSnapIndex == -1) {
+                mSnapIndex = adapterIndex;
+            } else if (adapterIndex > mSnapIndex) {
+                mSnapIndex = adapterIndex;
+            }
+        }
+
     }
 
     // layout an existing view
@@ -596,7 +665,7 @@
         View view = mOverlayAdapter.getView(adapterIndex, convertView, this);
         mOverlayViews.put(adapterIndex, new OverlayView(view, itemType));
 
-        final int index = getChildCount() - TOP_LAYER_COUNT;
+        final int index = BOTTOM_LAYER_VIEW_IDS.length;
 
         // Only re-attach if the view had previously been added to a view hierarchy.
         // Since external components can contribute to the scrap heap (addScrapView), we can't
@@ -610,6 +679,8 @@
                     true /* preventRequestLayout */);
         }
 
+        mAttachedOverlaySinceLastDraw = true;
+
         return view;
     }
 
diff --git a/src/com/android/mail/browse/ConversationOverlayItem.java b/src/com/android/mail/browse/ConversationOverlayItem.java
index 4c2d34a..c996184 100644
--- a/src/com/android/mail/browse/ConversationOverlayItem.java
+++ b/src/com/android/mail/browse/ConversationOverlayItem.java
@@ -91,4 +91,13 @@
     public void invalidateMeasurement() {
         mNeedsMeasure = true;
     }
+
+    public boolean canBecomeSnapHeader() {
+        return false;
+    }
+
+    public boolean canPushSnapHeader() {
+        return false;
+    }
+
 }
diff --git a/src/com/android/mail/browse/ConversationViewAdapter.java b/src/com/android/mail/browse/ConversationViewAdapter.java
index c1f8892..b5c1756 100644
--- a/src/com/android/mail/browse/ConversationViewAdapter.java
+++ b/src/com/android/mail/browse/ConversationViewAdapter.java
@@ -167,6 +167,17 @@
                 mExpanded = expanded;
             }
         }
+
+        @Override
+        public boolean canBecomeSnapHeader() {
+            return isExpanded();
+        }
+
+        @Override
+        public boolean canPushSnapHeader() {
+            return true;
+        }
+
     }
 
     public class MessageFooterItem extends ConversationOverlayItem {
@@ -256,15 +267,21 @@
         public int getEnd() {
             return mEnd;
         }
+
+        @Override
+        public boolean canPushSnapHeader() {
+            return true;
+        }
     }
 
     public ConversationViewAdapter(Context context, Account account, LoaderManager loaderManager,
             MessageHeaderViewCallbacks messageCallbacks,
             ContactInfoSource contactInfoSource,
             ConversationViewHeaderCallbacks convCallbacks,
-            SuperCollapsedBlock.OnClickListener scbListener, Map<String, Address> addressCache) {
+            SuperCollapsedBlock.OnClickListener scbListener, Map<String, Address> addressCache,
+            FormattedDateBuilder dateBuilder) {
         mContext = context;
-        mDateBuilder = new FormattedDateBuilder(context);
+        mDateBuilder = dateBuilder;
         mAccount = account;
         mLoaderManager = loaderManager;
         mMessageCallbacks = messageCallbacks;
diff --git a/src/com/android/mail/browse/MessageHeaderView.java b/src/com/android/mail/browse/MessageHeaderView.java
index e304c05..18ae53f 100644
--- a/src/com/android/mail/browse/MessageHeaderView.java
+++ b/src/com/android/mail/browse/MessageHeaderView.java
@@ -177,6 +177,8 @@
 
     private AsyncQueryHandler mQueryHandler;
 
+    private boolean mObservingContactInfo;
+
     private final DataSetObserver mContactInfoObserver = new DataSetObserver() {
         @Override
         public void onChanged() {
@@ -320,7 +322,10 @@
         mMessageHeaderItem = null;
         mMessage = null;
 
-        mContactInfoSource.unregisterObserver(mContactInfoObserver);
+        if (mObservingContactInfo) {
+            mContactInfoSource.unregisterObserver(mContactInfoObserver);
+            mObservingContactInfo = false;
+        }
     }
 
     public void renderUpperHeaderFrom(MessageHeaderView other) {
@@ -350,6 +355,10 @@
 
     public void bind(MessageHeaderItem headerItem, boolean defaultReplyAll,
             boolean measureOnly) {
+        if (mMessageHeaderItem != null && mMessageHeaderItem == headerItem) {
+            return;
+        }
+
         Timer t = new Timer();
         t.start(HEADER_RENDER_TAG);
 
@@ -415,9 +424,15 @@
         mStarView.setContentDescription(getResources().getString(
                 mStarView.isSelected() ? R.string.remove_star : R.string.add_star));
 
-        if (!measureOnly) {
+        if (measureOnly) {
+            // avoid leaving any state around that would interfere with future regular bind() calls
+            unbind();
+        } else {
             updateContactInfo();
-            mContactInfoSource.registerObserver(mContactInfoObserver);
+            if (!mObservingContactInfo) {
+                mContactInfoSource.registerObserver(mContactInfoObserver);
+                mObservingContactInfo = true;
+            }
         }
 
         t.pause(HEADER_RENDER_TAG);
diff --git a/src/com/android/mail/ui/ConversationViewFragment.java b/src/com/android/mail/ui/ConversationViewFragment.java
index e2a9890..5fb806e 100644
--- a/src/com/android/mail/ui/ConversationViewFragment.java
+++ b/src/com/android/mail/ui/ConversationViewFragment.java
@@ -51,6 +51,7 @@
 
 import com.android.mail.ContactInfo;
 import com.android.mail.ContactInfoSource;
+import com.android.mail.FormattedDateBuilder;
 import com.android.mail.R;
 import com.android.mail.SenderInfoLoader;
 import com.android.mail.browse.ConversationContainer;
@@ -64,6 +65,7 @@
 import com.android.mail.browse.MessageCursor;
 import com.android.mail.browse.MessageCursor.ConversationMessage;
 import com.android.mail.browse.MessageCursor.ConversationController;
+import com.android.mail.browse.MessageHeaderView;
 import com.android.mail.browse.MessageHeaderView.MessageHeaderViewCallbacks;
 import com.android.mail.browse.SuperCollapsedBlock;
 import com.android.mail.browse.WebViewContextMenu;
@@ -242,10 +244,20 @@
         }
         mTemplates = new HtmlConversationTemplates(mContext);
         mAccount = mAccountObserver.initialize(mActivity.getAccountController());
+
+        final FormattedDateBuilder dateBuilder = new FormattedDateBuilder(mContext);
+
         mAdapter = new ConversationViewAdapter(mActivity.getActivityContext(), mAccount,
-                getLoaderManager(), this, mContactLoaderCallbacks, this, this, mAddressCache);
+                getLoaderManager(), this, mContactLoaderCallbacks, this, this, mAddressCache,
+                dateBuilder);
         mConversationContainer.setOverlayAdapter(mAdapter);
 
+        // set up snap header (the adapter usually does this with the other ones)
+        final MessageHeaderView snapHeader = mConversationContainer.getSnapHeader();
+        snapHeader.initialize(dateBuilder, mAccount, mAddressCache);
+        snapHeader.setCallbacks(this);
+        snapHeader.setContactInfoSource(mContactLoaderCallbacks);
+
         mMaxAutoLoadMessages = getResources().getInteger(R.integer.max_auto_load_messages);
 
         mWebView.setOnCreateContextMenuListener(new WebViewContextMenu(activity));
diff --git a/src/com/android/mail/ui/MailActionBarView.java b/src/com/android/mail/ui/MailActionBarView.java
index 879456b..82ac5a3 100644
--- a/src/com/android/mail/ui/MailActionBarView.java
+++ b/src/com/android/mail/ui/MailActionBarView.java
@@ -453,7 +453,7 @@
             mSearch.collapseActionView();
             mSearchWidget.setQuery("", false);
         }
-        mActivity.onSearchRequested(query);
+        mActivity.onSearchRequested(query.trim());
         return true;
     }
 
@@ -507,7 +507,9 @@
             return true;
         }
         collapseSearch();
+        // what is in the text field
         String queryText = mSearchWidget.getQuery().toString();
+        // What the suggested query is
         String query = c.getString(c.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY));
         if (!TextUtils.isEmpty(queryText)) {
             final int queryTokenIndex = queryText
@@ -529,7 +531,7 @@
                 query = query.substring(0, start) + query.substring(start + queryText.length());
             }
         }
-        mController.onSearchRequested(query);
+        mController.onSearchRequested(query.trim());
         return true;
     }