Merge "Improve swipe mechanics."
diff --git a/res/values/dimen.xml b/res/values/dimen.xml
index 714532b..78f7467 100644
--- a/res/values/dimen.xml
+++ b/res/values/dimen.xml
@@ -81,4 +81,7 @@
     <dimen name="widget_margin_bottom">22dip</dimen>
     <dimen name="conversation_item_height">70sp</dimen>
     <dimen name="conversation_item_height_wide">64sp</dimen>
+    <dimen name="min_swipe">10dip</dimen>
+    <dimen name="min_vert">10dip</dimen>
+    <dimen name="min_lock">20dip</dimen>
 </resources>
diff --git a/src/com/android/mail/ui/AnimatedAdapter.java b/src/com/android/mail/ui/AnimatedAdapter.java
index d0f7b17..1e8f068 100644
--- a/src/com/android/mail/ui/AnimatedAdapter.java
+++ b/src/com/android/mail/ui/AnimatedAdapter.java
@@ -251,7 +251,7 @@
             // The undo animation consists of fading in the conversation that
             // had been destroyed.
             ConversationItemView convView = (ConversationItemView) super.getView(position, null,
-                    mListView);
+                    parent);
             convView.bind(conversation, mViewMode, mBatchConversations, mFolder,
                     mCachedSettings != null ? !mCachedSettings.hideCheckboxes : false,
                     mSwipeEnabled);
diff --git a/src/com/android/mail/ui/SwipeHelper.java b/src/com/android/mail/ui/SwipeHelper.java
index 8a50d49..bd513cd 100644
--- a/src/com/android/mail/ui/SwipeHelper.java
+++ b/src/com/android/mail/ui/SwipeHelper.java
@@ -25,10 +25,10 @@
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.graphics.RectF;
 import android.util.Log;
-import android.view.animation.LinearInterpolator;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.View;
+import android.view.animation.LinearInterpolator;
 
 import com.android.mail.browse.ConversationItemView;
 
@@ -42,6 +42,7 @@
     private static final boolean CONSTRAIN_SWIPE = true;
     private static final boolean FADE_OUT_DURING_SWIPE = true;
     private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
+    private static final boolean LOG_SWIPE_DISMISS_VELOCITY = false; // STOPSHIP - DEBUG ONLY
 
     public static final int X = 0;
     public static final int Y = 1;
@@ -52,7 +53,7 @@
     private int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms
     private int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms
     private int MAX_DISMISS_VELOCITY = 2000; // dp/sec
-    private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 150; // ms
+    private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 1; // ms
 
     public static float ALPHA_FADE_START = 0f; // fraction of thumbnail width
                                                  // where fade starts
@@ -65,7 +66,7 @@
     private int mSwipeDirection;
     private VelocityTracker mVelocityTracker;
 
-    private float mInitialTouchPos;
+    private float mInitialTouchPosX;
     private boolean mDragging;
     private ConversationItemView mCurrView;
     private View mCurrAnimView;
@@ -74,15 +75,22 @@
     private float mLastY;
     private Collection<ConversationItemView> mAssociatedViews;
     private final float mScrollSlop;
+    private float mInitialTouchPosY;
+    private float mMinSwipe;
+    private float mMinVert;
+    private float mMinLock;
 
     public SwipeHelper(int swipeDirection, Callback callback, float densityScale,
-            float pagingTouchSlop, float scrollSlop) {
+            float pagingTouchSlop, float scrollSlop, float minSwipe, float minVert, float minLock) {
         mCallback = callback;
         mSwipeDirection = swipeDirection;
         mVelocityTracker = VelocityTracker.obtain();
         mDensityScale = densityScale;
         mPagingTouchSlop = pagingTouchSlop;
         mScrollSlop = scrollSlop;
+        mMinSwipe = minSwipe;
+        mMinVert = minVert;
+        mMinLock = minLock;
     }
 
     public void setDensityScale(float densityScale) {
@@ -198,7 +206,8 @@
                     mCurrAnimView = mCallback.getChildContentView(mCurrView);
                     mCanCurrViewBeDimissed = mCallback.canChildBeDismissed(mCurrView);
                     mVelocityTracker.addMovement(ev);
-                    mInitialTouchPos = getPos(ev);
+                    mInitialTouchPosX = getPos(ev);
+                    mInitialTouchPosY = ev.getY();
                 }
                 break;
             case MotionEvent.ACTION_MOVE:
@@ -214,14 +223,15 @@
                     }
                     mVelocityTracker.addMovement(ev);
                     float pos = getPos(ev);
-                    float delta = pos - mInitialTouchPos;
+                    float delta = pos - mInitialTouchPosX;
                     if (Math.abs(delta) > mPagingTouchSlop) {
                         if (mCallback.getSelectionSet().isEmpty()
                                 || (!mCallback.getSelectionSet().isEmpty()
                                         && mCurrView.isChecked())) {
                             mCallback.onBeginDrag(mCurrView);
                             mDragging = true;
-                            mInitialTouchPos = getPos(ev) - getTranslation(mCurrAnimView);
+                            mInitialTouchPosX = getPos(ev) - getTranslation(mCurrAnimView);
+                            mInitialTouchPosY = ev.getY();
                         }
                     }
                 }
@@ -260,12 +270,14 @@
         animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
         ObjectAnimator anim = createDismissAnimation(animView, newPos, duration);
         anim.addListener(new AnimatorListenerAdapter() {
+            @Override
             public void onAnimationEnd(Animator animation) {
                 mCallback.onChildDismissed(view);
                 mCurrView.setLayerType(View.LAYER_TYPE_NONE, null);
             }
         });
         anim.addUpdateListener(new AnimatorUpdateListener() {
+            @Override
             public void onAnimationUpdate(ValueAnimator animation) {
                 if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
                     animView.setAlpha(getAlphaForOffset(animView));
@@ -278,6 +290,7 @@
 
     private void dismissChildren(final Collection<ConversationItemView> views, float velocity) {
         AnimatorListenerAdapter listener = new AnimatorListenerAdapter() {
+            @Override
             public void onAnimationEnd(Animator animation) {
                 mCallback.onChildrenDismissed(views);
                 mCurrView.setLayerType(View.LAYER_TYPE_NONE, null);
@@ -293,6 +306,7 @@
             view.setLayerType(View.LAYER_TYPE_HARDWARE, null);
             anim = createDismissAnimation(view, newPos, duration);
             anim.addUpdateListener(new AnimatorUpdateListener() {
+                @Override
                 public void onAnimationUpdate(ValueAnimator animation) {
                     if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
                         view.setAlpha(getAlphaForOffset(view));
@@ -340,6 +354,7 @@
         int duration = SNAP_ANIM_LEN;
         anim.setDuration(duration);
         anim.addUpdateListener(new AnimatorUpdateListener() {
+            @Override
             public void onAnimationUpdate(ValueAnimator animation) {
                 if (FADE_OUT_DURING_SWIPE && canAnimViewBeDismissed) {
                     animView.setAlpha(getAlphaForOffset(animView));
@@ -365,27 +380,41 @@
             case MotionEvent.ACTION_OUTSIDE:
             case MotionEvent.ACTION_MOVE:
                 if (mCurrView != null) {
-                    float delta = getPos(ev) - mInitialTouchPos;
-                    // don't let items that can't be dismissed be dragged more than
-                    // maxScrollDistance
+                    float deltaX = getPos(ev) - mInitialTouchPosX;
+                    float deltaY = Math.abs(ev.getY() - mInitialTouchPosY);
+                    // If the user has gone vertical and not gone horizontal AT
+                    // LEAST minBeforeLock, switch to scroll. Otherwise, cancel
+                    // the swipe.
+                    if (deltaY > mMinVert && (Math.abs(deltaX)) < mMinLock) {
+                        return false;
+                    }
+                    float minDistance = mMinSwipe;
+                    if (Math.abs(deltaX) < minDistance) {
+                        // Don't start the drag until at least X distance has
+                        // occurred.
+                        return true;
+                    }
+                    // don't let items that can't be dismissed be dragged more
+                    // than maxScrollDistance
                     if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissed(mCurrView)) {
                         float size = getSize(mCurrAnimView);
                         float maxScrollDistance = 0.15f * size;
-                        if (Math.abs(delta) >= size) {
-                            delta = delta > 0 ? maxScrollDistance : -maxScrollDistance;
+                        if (Math.abs(deltaX) >= size) {
+                            deltaX = deltaX > 0 ? maxScrollDistance : -maxScrollDistance;
                         } else {
-                            delta = maxScrollDistance * (float) Math.sin((delta/size)*(Math.PI/2));
+                            deltaX = maxScrollDistance
+                                    * (float) Math.sin((deltaX / size) * (Math.PI / 2));
                         }
                     }
                     if (mAssociatedViews != null && mAssociatedViews.size() > 1) {
                         for (View v : mAssociatedViews) {
-                            setTranslation(v, delta);
+                            setTranslation(v, deltaX);
                         }
                     } else {
-                        setTranslation(mCurrAnimView, delta);
+                        setTranslation(mCurrAnimView, deltaX);
                     }
                     if (FADE_OUT_DURING_SWIPE && mCanCurrViewBeDimissed) {
-                        if (mAssociatedViews != null  && mAssociatedViews.size() > 1) {
+                        if (mAssociatedViews != null && mAssociatedViews.size() > 1) {
                             for (View v : mAssociatedViews) {
                                 v.setAlpha(getAlphaForOffset(mCurrAnimView));
                             }
@@ -406,14 +435,27 @@
                     float perpendicularVelocity = getPerpendicularVelocity(mVelocityTracker);
 
                     // Decide whether to dismiss the current view
-                    boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH &&
-                            Math.abs(getTranslation(mCurrAnimView)) > 0.4 * getSize(mCurrAnimView);
-                    boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity) &&
-                            (Math.abs(velocity) > Math.abs(perpendicularVelocity)) &&
-                            (velocity > 0) == (getTranslation(mCurrAnimView) > 0);
+                    // Tweak constants below as required to prevent erroneous
+                    // swipe/dismiss
+                    float translation = Math.abs(getTranslation(mCurrAnimView));
+                    float currAnimViewSize = getSize(mCurrAnimView);
+                    // Long swipe = translation of .4 * width
+                    boolean childSwipedFarEnough = DISMISS_IF_SWIPED_FAR_ENOUGH
+                            && translation > 0.4 * currAnimViewSize;
+                    // Fast swipe = > escapeVelocity and translation of .1 *
+                    // width
+                    boolean childSwipedFastEnough = (Math.abs(velocity) > escapeVelocity)
+                            && (Math.abs(velocity) > Math.abs(perpendicularVelocity))
+                            && (velocity > 0) == (getTranslation(mCurrAnimView) > 0)
+                            && translation > 0.05 * currAnimViewSize;
+                    if (LOG_SWIPE_DISMISS_VELOCITY) {
+                        Log.v(TAG, "Swipe/Dismiss: " + velocity + "/" + escapeVelocity + "/"
+                                + perpendicularVelocity + ", x: " + translation + "/"
+                                + currAnimViewSize);
+                    }
 
-                    boolean dismissChild = mCallback.canChildBeDismissed(mCurrView) &&
-                            (childSwipedFastEnough || childSwipedFarEnough);
+                    boolean dismissChild = mCallback.canChildBeDismissed(mCurrView)
+                            && (childSwipedFastEnough || childSwipedFarEnough);
 
                     if (dismissChild) {
                         if (mAssociatedViews != null && mAssociatedViews.size() > 1) {
diff --git a/src/com/android/mail/ui/SwipeableListView.java b/src/com/android/mail/ui/SwipeableListView.java
index 5f53656..a4733f4 100644
--- a/src/com/android/mail/ui/SwipeableListView.java
+++ b/src/com/android/mail/ui/SwipeableListView.java
@@ -61,8 +61,11 @@
         super(context, attrs, defStyle);
         float densityScale = getResources().getDisplayMetrics().density;
         float scrollSlop = context.getResources().getInteger(R.integer.swipeScrollSlop);
+        float minSwipe = context.getResources().getDimension(R.dimen.min_swipe);
+        float minVert = context.getResources().getDimension(R.dimen.min_vert);
+        float minLock = context.getResources().getDimension(R.dimen.min_lock);
         mSwipeHelper = new SwipeHelper(SwipeHelper.X, this, densityScale, densityScale,
-                scrollSlop);
+                scrollSlop, minSwipe, minVert, minLock);
     }
 
     /**
@@ -183,6 +186,9 @@
 
     private Conversation getConversation(View view) {
         Conversation c = ((ConversationItemView) view).getConversation();
+        if (view.getParent() == null) {
+            return c;
+        }
         c.position = getPositionForView(view);
         return c;
     }