Merge "If the size is 0, dont show anything." into jb-ub-mail
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7fc9f0b..aa6693b 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -555,9 +555,9 @@
     <!-- Shown in a toast to acknowledge always showing images for a sender [CHAR LIMIT=100] -->
     <string name="always_show_images_toast">Pictures from this sender will be shown automatically.</string>
     <!-- Display format of an email recipient, displayed in expanded message details [CHAR LIMIT=10] -->
-    <string name="address_display_format"><xliff:g id="name">%1$s</xliff:g> &lt;<xliff:g id="email">%2$s</xliff:g>&gt;</string>
+    <string name="address_display_format"><xliff:g id="name">%1$s</xliff:g> <xliff:g id="email">%2$s</xliff:g></string>
     <!-- Display format of an email sender if the message has a via domain set, displayed in expanded message details [CHAR LIMIT=15] -->
-    <string name="address_display_format_with_via_domain"><xliff:g id="name">%1$s</xliff:g> &lt;<xliff:g id="email">%2$s</xliff:g>&gt; via <xliff:g id="via_domain">%3$s</xliff:g></string>
+    <string name="address_display_format_with_via_domain"><xliff:g id="name">%1$s</xliff:g> <xliff:g id="email">%2$s</xliff:g> via <xliff:g id="via_domain">%3$s</xliff:g></string>
     <!-- Displayed for one second after user saves message as draft [CHAR LIMIT=50]-->
     <string name="message_saved">Message saved as draft.</string>
     <!-- Displayed for one second while message is being sent [CHAR LIMIT=50]-->
diff --git a/src/com/android/mail/browse/ConversationItemView.java b/src/com/android/mail/browse/ConversationItemView.java
index 53251d7..edbc3b2 100644
--- a/src/com/android/mail/browse/ConversationItemView.java
+++ b/src/com/android/mail/browse/ConversationItemView.java
@@ -21,6 +21,8 @@
 import android.animation.Animator.AnimatorListener;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
+import android.content.ClipData;
+import android.content.ClipData.Item;
 import android.content.Context;
 import android.content.res.Resources;
 import android.database.Cursor;
@@ -30,6 +32,7 @@
 import android.graphics.Color;
 import android.graphics.LinearGradient;
 import android.graphics.Paint;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Shader;
 import android.graphics.Typeface;
@@ -48,6 +51,7 @@
 import android.text.style.StyleSpan;
 import android.util.SparseArray;
 import android.view.Gravity;
+import android.view.DragEvent;
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
@@ -71,6 +75,7 @@
 import com.android.mail.ui.SwipeableListView;
 import com.android.mail.ui.ViewMode;
 import com.android.mail.utils.LogTag;
+import com.android.mail.utils.LogUtils;
 import com.android.mail.utils.Utils;
 import com.google.common.annotations.VisibleForTesting;
 
@@ -106,7 +111,6 @@
 
     private static String sSendersSplitToken;
     private static String sElidedPaddingToken;
-    private static String sEllipsis;
 
     // Static colors.
     private static int sDefaultTextColor;
@@ -160,6 +164,8 @@
     private boolean mPriorityMarkersEnabled;
     private boolean mCheckboxesEnabled;
     private boolean mSwipeEnabled;
+    private int mLastTouchX;
+    private int mLastTouchY;
     private AnimatedAdapter mAdapter;
     private int mAnimatedHeight = -1;
     private String mAccount;
@@ -372,7 +378,6 @@
             // Initialize static color.
             sSendersSplitToken = res.getString(R.string.senders_split_token);
             sElidedPaddingToken = res.getString(R.string.elided_padding_token);
-            sEllipsis = res.getString(R.string.ellipsis);
             sAnimatingBackgroundColor = res.getColor(R.color.animating_item_background_color);
             sSendersTextViewTopPadding = res.getDimensionPixelSize
                     (R.dimen.senders_textview_top_padding);
@@ -1172,23 +1177,26 @@
      * Toggle the check mark on this view and update the conversation
      */
     public void toggleCheckMark() {
-        if (mHeader != null && mHeader.conversation != null) {
-            mChecked = !mChecked;
-            Conversation conv = mHeader.conversation;
-            // Set the list position of this item in the conversation
-            ListView listView = getListView();
-            conv.position = mChecked && listView != null ? listView.getPositionForView(this)
-                    : Conversation.NO_POSITION;
-            if (mSelectedConversationSet != null) {
-                mSelectedConversationSet.toggle(this, conv);
+        ViewMode mode = mActivity.getViewMode();
+        if (!mTabletDevice || !mode.isListMode()) {
+            if (mHeader != null && mHeader.conversation != null) {
+                mChecked = !mChecked;
+                Conversation conv = mHeader.conversation;
+                // Set the list position of this item in the conversation
+                ListView listView = getListView();
+                conv.position = mChecked && listView != null ? listView.getPositionForView(this)
+                        : Conversation.NO_POSITION;
+                if (mSelectedConversationSet != null) {
+                    mSelectedConversationSet.toggle(this, conv);
+                }
+                // We update the background after the checked state has changed
+                // now that we have a selected background asset. Setting the background
+                // usually waits for a layout pass, but we don't need a full layout,
+                // just an update to the background.
+                requestLayout();
             }
-            // We update the background after the checked state has changed now
-            // that
-            // we have a selected background asset. Setting the background
-            // usually
-            // waits for a layout pass, but we don't need a full layout, just an
-            // update to the background.
-            requestLayout();
+        } else {
+            beginDragMode();
         }
     }
 
@@ -1243,6 +1251,8 @@
     public boolean onTouchEvent(MotionEvent event) {
         int x = (int) event.getX();
         int y = (int) event.getY();
+        mLastTouchX = x;
+        mLastTouchY = y;
         if (!mSwipeEnabled) {
             return onTouchEventNoSwipe(event);
         }
@@ -1298,29 +1308,26 @@
     }
 
     private boolean onTouchEventNoSwipe(MotionEvent event) {
-        boolean handled = true;
+        boolean handled = false;
 
         int x = (int) event.getX();
         int y = (int) event.getY();
+        mLastTouchX = x;
+        mLastTouchY = y;
         switch (event.getAction()) {
             case MotionEvent.ACTION_DOWN:
                 mDownEvent = true;
-                // In order to allow the down event and subsequent move events
-                // to bubble to the swipe handler, we need to return that all
-                // down events are handled.
-                handled = isTouchInCheckmark(x, y) || isTouchInStar(x, y);
+                if (isTouchInCheckmark(x, y) || isTouchInStar(x, y)) {
+                    handled = true;
+                }
                 break;
+
             case MotionEvent.ACTION_CANCEL:
                 mDownEvent = false;
                 break;
+
             case MotionEvent.ACTION_UP:
                 if (mDownEvent) {
-                    // ConversationItemView gets the first chance to handle up
-                    // events if there was a down event and there was no move
-                    // event in between. In this case, ConversationItemView
-                    // received the down event, and then an up event in the
-                    // same location (+/- slop). Treat this as a click on the
-                    // view or on a specific part of the view.
                     if (isTouchInCheckmark(x, y)) {
                         // Touch on the check mark
                         toggleCheckMark();
@@ -1329,16 +1336,15 @@
                         toggleStar();
                     }
                     handled = true;
-                } else {
-                    // There was no down event that this view was made aware of,
-                    // therefore it cannot handle it.
-                    handled = false;
                 }
                 break;
         }
 
-        // Let View try to handle it as well.
-        return handled || super.onTouchEvent(event);
+        if (!handled) {
+            handled = super.onTouchEvent(event);
+        }
+
+        return handled;
     }
 
     /**
@@ -1491,4 +1497,105 @@
     public View getSwipeableView() {
         return this;
     }
+
+    /**
+     * Select the current conversation.
+     */
+    private void selectConversation() {
+        if (!mSelectedConversationSet.containsKey(mHeader.conversation.id)) {
+            mChecked = !mChecked;
+            Conversation conv = mHeader.conversation;
+            // Set the list position of this item in the conversation
+            ListView listView = getListView();
+            conv.position = mChecked && listView != null ? listView.getPositionForView(this)
+                    : Conversation.NO_POSITION;
+            if (mSelectedConversationSet != null) {
+                mSelectedConversationSet.toggle(this, conv);
+            }
+        }
+    }
+
+    /**
+     * Begin drag mode. Keep the conversation selected (NOT toggle selection) and start drag.
+     */
+    private void beginDragMode() {
+        selectConversation();
+
+        // Clip data has form: [conversations_uri, conversationId1,
+        // maxMessageId1, label1, conversationId2, maxMessageId2, label2, ...]
+        final int count = mSelectedConversationSet.size();
+        String description = Utils.formatPlural(mContext, R.plurals.move_conversation, count);
+
+        final ClipData data = ClipData.newUri(mContext.getContentResolver(), description,
+                Conversation.MOVE_CONVERSATIONS_URI);
+        for (Conversation conversation : mSelectedConversationSet.values()) {
+            data.addItem(new Item(String.valueOf(conversation.position)));
+        }
+        // Protect against non-existent views: only happens for monkeys
+        final int width = this.getWidth();
+        final int height = this.getHeight();
+        final boolean isDimensionNegative = (width < 0) || (height < 0);
+        if (isDimensionNegative) {
+            LogUtils.e(LOG_TAG, "ConversationItemView: dimension is negative: "
+                        + "width=%d, height=%d", width, height);
+            return;
+        }
+        mActivity.startDragMode();
+        // Start drag mode
+        startDrag(data, new ShadowBuilder(this, count, mLastTouchX, mLastTouchY), null, 0);
+    }
+
+    /**
+     * Handles the drag event.
+     *
+     * @param event the drag event to be handled
+     */
+    @Override
+    public boolean onDragEvent(DragEvent event) {
+        switch (event.getAction()) {
+            case DragEvent.ACTION_DRAG_ENDED:
+                mActivity.stopDragMode();
+                return true;
+        }
+        return false;
+    }
+
+    private class ShadowBuilder extends DragShadowBuilder {
+        private final Drawable mBackground;
+
+        private final View mView;
+        private final String mDragDesc;
+        private final int mTouchX;
+        private final int mTouchY;
+        private int mDragDescX;
+        private int mDragDescY;
+
+        public ShadowBuilder(View view, int count, int touchX, int touchY) {
+            super(view);
+            mView = view;
+            mBackground = mView.getResources().getDrawable(R.drawable.list_pressed_holo);
+            mDragDesc = Utils.formatPlural(mView.getContext(), R.plurals.move_conversation, count);
+            mTouchX = touchX;
+            mTouchY = touchY;
+        }
+
+        @Override
+        public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
+            int width = mView.getWidth();
+            int height = mView.getHeight();
+            mDragDescX = mCoordinates.sendersX;
+            mDragDescY = getPadding(height, mCoordinates.subjectFontSize)
+                    - mCoordinates.subjectAscent;
+            shadowSize.set(width, height);
+            shadowTouchPoint.set(mTouchX, mTouchY);
+        }
+
+        @Override
+        public void onDrawShadow(Canvas canvas) {
+            mBackground.setBounds(0, 0, mView.getWidth(), mView.getHeight());
+            mBackground.draw(canvas);
+            sPaint.setTextSize(mCoordinates.subjectFontSize);
+            canvas.drawText(mDragDesc, mDragDescX, mDragDescY, sPaint);
+        }
+    }
 }
diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java
index 63cdbd5..efc4019 100644
--- a/src/com/android/mail/ui/AbstractActivityController.java
+++ b/src/com/android/mail/ui/AbstractActivityController.java
@@ -251,6 +251,7 @@
     // will just run the next time the user opens the app.
     private AsyncTask<String, Void, Void> mEnableShareIntents;
     private Folder mFolderListFolder;
+    private boolean mIsDragHappening;
     public static final String SYNC_ERROR_DIALOG_FRAGMENT_TAG = "SyncErrorDialogFragment";
 
     public AbstractActivityController(MailActivity activity, ViewMode viewMode) {
@@ -1927,7 +1928,7 @@
 
     @Override
     public final void onRefreshRequired() {
-        if (isAnimating()) {
+        if (isAnimating() || isDragging()) {
             LogUtils.d(LOG_TAG, "onRefreshRequired: delay until animating done");
             return;
         }
@@ -1937,6 +1938,29 @@
         }
     }
 
+    @Override
+    public void startDragMode() {
+        mIsDragHappening = true;
+    }
+
+    @Override
+    public void stopDragMode() {
+        mIsDragHappening = false;
+        if (mConversationListCursor.isRefreshReady()) {
+            LogUtils.d(LOG_TAG, "Stopped animating: try sync");
+            onRefreshReady();
+        }
+
+        if (mConversationListCursor.isRefreshRequired()) {
+            LogUtils.d(LOG_TAG, "Stopped animating: refresh");
+            mConversationListCursor.refresh();
+        }
+    }
+
+    private boolean isDragging() {
+        return mIsDragHappening;
+    }
+
     private boolean isAnimating() {
         boolean isAnimating = false;
         ConversationListFragment convListFragment = getConversationListFragment();
diff --git a/src/com/android/mail/ui/ActivityController.java b/src/com/android/mail/ui/ActivityController.java
index 524ad76..59d6776 100644
--- a/src/com/android/mail/ui/ActivityController.java
+++ b/src/com/android/mail/ui/ActivityController.java
@@ -308,4 +308,14 @@
      * Handles the animation end of the animated adapter.
      */
     void onAnimationEnd(AnimatedAdapter animatedAdapter);
+
+    /**
+     * Called when the user has started a drag/ drop gesture.
+     */
+    void startDragMode();
+
+    /**
+     * Called when the user has ended drag/drop.
+     */
+    void stopDragMode();
 }
diff --git a/src/com/android/mail/ui/ControllableActivity.java b/src/com/android/mail/ui/ControllableActivity.java
index d4046a6..63d3318 100644
--- a/src/com/android/mail/ui/ControllableActivity.java
+++ b/src/com/android/mail/ui/ControllableActivity.java
@@ -105,4 +105,8 @@
      * @return
      */
     AccountController getAccountController();
+
+    void startDragMode();
+
+    void stopDragMode();
 }
diff --git a/src/com/android/mail/ui/FolderSelectionActivity.java b/src/com/android/mail/ui/FolderSelectionActivity.java
index 1843d65..c698e6d 100644
--- a/src/com/android/mail/ui/FolderSelectionActivity.java
+++ b/src/com/android/mail/ui/FolderSelectionActivity.java
@@ -341,4 +341,14 @@
     public void onFooterViewLoadMoreClick(Folder folder) {
         // Unsupported
     }
+
+    @Override
+    public void startDragMode() {
+        // Unsupported
+    }
+
+    @Override
+    public void stopDragMode() {
+        // Unsupported
+    }
 }
diff --git a/src/com/android/mail/ui/MailActivity.java b/src/com/android/mail/ui/MailActivity.java
index 0418f8d..4a4c80c 100644
--- a/src/com/android/mail/ui/MailActivity.java
+++ b/src/com/android/mail/ui/MailActivity.java
@@ -423,4 +423,14 @@
     public void onFooterViewLoadMoreClick(Folder folder) {
         mController.onFooterViewLoadMoreClick(folder);
     }
+
+    @Override
+    public void startDragMode() {
+        mController.startDragMode();
+    }
+
+    @Override
+    public void stopDragMode() {
+        mController.stopDragMode();
+    }
 }