add tab dragging

    Bug: 5081671
    enable dragging tabs to close them
    animations are not correct yet and will be fixed later

Change-Id: Ib0a4ca07706fd73464e307f2061c4246863b9ec8
diff --git a/src/com/android/browser/NavScreen.java b/src/com/android/browser/NavScreen.java
index ee20535..f52ef88 100644
--- a/src/com/android/browser/NavScreen.java
+++ b/src/com/android/browser/NavScreen.java
@@ -23,8 +23,8 @@
 import android.view.Menu;
 import android.view.MenuItem;
 import android.view.View;
-import android.view.ViewConfiguration;
 import android.view.View.OnClickListener;
+import android.view.ViewConfiguration;
 import android.view.ViewGroup;
 import android.webkit.WebView;
 import android.widget.BaseAdapter;
@@ -37,6 +37,8 @@
 import android.widget.RelativeLayout;
 import android.widget.TextView;
 
+import com.android.browser.NavTabGallery.OnRemoveListener;
+
 public class NavScreen extends RelativeLayout
         implements OnClickListener, OnMenuItemClickListener {
 
@@ -124,6 +126,12 @@
                 ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL);
         // update state for active tab
         mScroller.setSelection(mUiController.getTabControl().getTabPosition(mUi.getActiveTab()));
+        mScroller.setOnRemoveListener(new OnRemoveListener() {
+            public void onRemovePosition(int pos) {
+                Tab tab = mAdapter.getItem(pos);
+                onCloseTab(tab);
+            }
+        });
         mNeedsMenu = !ViewConfiguration.get(getContext()).hasPermanentMenuKey();
         if (!mNeedsMenu) {
             mMore.setVisibility(View.GONE);
diff --git a/src/com/android/browser/NavTabGallery.java b/src/com/android/browser/NavTabGallery.java
index 3014eaf..0cd1f82 100644
--- a/src/com/android/browser/NavTabGallery.java
+++ b/src/com/android/browser/NavTabGallery.java
@@ -16,8 +16,12 @@
 
 package com.android.browser;
 
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.util.AttributeSet;
+import android.view.MotionEvent;
 import android.view.View;
 
 import com.android.browser.view.Gallery;
@@ -27,6 +31,16 @@
  */
 public class NavTabGallery extends Gallery {
 
+    interface OnRemoveListener {
+        public void onRemovePosition(int position);
+    }
+
+    // after drag animation velocity in pixels/sec
+    private static final float MIN_VELOCITY = 1500;
+
+    private OnRemoveListener mRemoveListener;
+    private boolean mBlockUpCallback;
+
     public NavTabGallery(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
     }
@@ -39,6 +53,10 @@
         super(context);
     }
 
+    public void setOnRemoveListener(OnRemoveListener l) {
+        mRemoveListener = l;
+    }
+
     protected void setSelection(int ix) {
         super.setSelectedPositionInt(ix);
     }
@@ -55,4 +73,76 @@
         return getSelectedView();
     }
 
+    @Override
+    protected void onOrthoDrag(View v, MotionEvent down, MotionEvent move,
+            float distance) {
+        offsetView(v, - distance);
+    }
+
+    @Override
+    protected void onOrthoFling(View v, MotionEvent down, MotionEvent move,
+            float velocity) {
+        if (Math.abs(velocity) > MIN_VELOCITY) {
+            mBlockUpCallback = true;
+            animateOut(v, velocity);
+        }
+    }
+
+    @Override
+    protected void onUp(View downView) {
+        if (mBlockUpCallback) {
+            mBlockUpCallback = false;
+            return;
+        }
+        if (mIsOrthoDragged && downView != null) {
+            // offset
+            int diff = calculateTop(downView, false) - (mHorizontal ? downView.getTop()
+                    : downView.getLeft());
+            if (Math.abs(diff) > (mHorizontal ? downView.getHeight() : downView.getWidth()) / 2) {
+                // remove it
+                animateOut(downView, - Math.signum(diff) * MIN_VELOCITY);
+            } else {
+                // snap back
+                offsetView(downView, diff);
+            }
+        } else {
+            super.onUp(downView);
+        }
+    }
+
+    private void offsetView(View v, float distance) {
+        if (mHorizontal) {
+            v.offsetTopAndBottom((int) distance);
+        } else {
+            v.offsetLeftAndRight((int) distance);
+        }
+    }
+
+    private void animateOut(View v, float velocity) {
+        final int position = mDownTouchPosition;
+        int target = 0;
+        if (velocity < 0) {
+            target = mHorizontal ? -v.getHeight() :  - v.getWidth();
+        } else {
+            target = mHorizontal ? getHeight() : getWidth();
+        }
+        int distance = target - (mHorizontal ? v.getTop() : v.getLeft());
+        long duration = (long) (Math.abs(distance) * 1000 / Math.abs(velocity));
+        ObjectAnimator animator = null;
+        if (mHorizontal) {
+            animator = ObjectAnimator.ofFloat(v, TRANSLATION_Y, 0, target);
+        } else {
+            animator = ObjectAnimator.ofFloat(v, TRANSLATION_X, 0, target);
+        }
+        animator.setDuration(duration);
+        animator.addListener(new AnimatorListenerAdapter() {
+            public void onAnimationEnd(Animator a) {
+                if (mRemoveListener !=  null) {
+                    mRemoveListener.onRemovePosition(position);
+                }
+            }
+        });
+        animator.start();
+    }
+
 }
diff --git a/src/com/android/browser/view/Gallery.java b/src/com/android/browser/view/Gallery.java
index 0c73537..d9c172e 100644
--- a/src/com/android/browser/view/Gallery.java
+++ b/src/com/android/browser/view/Gallery.java
@@ -63,7 +63,7 @@
 
     private RecycleBin mRecycler;
 
-    private boolean mHorizontal;
+    protected boolean mHorizontal;
     private int mFirstPosition;
     private int mItemCount;
     private boolean mDataChanged;
@@ -82,8 +82,8 @@
 
     private GestureDetector mGestureDetector;
 
-    private int mDownTouchPosition;
-    private View mDownTouchView;
+    protected int mDownTouchPosition;
+    protected View mDownTouchView;
     private FlingRunnable mFlingRunnable = new FlingRunnable();
 
     private OnItemSelectedListener mOnItemSelectedListener;
@@ -114,12 +114,14 @@
     private boolean mIsFirstScroll;
 
     private boolean mIsBeingDragged;
+    protected boolean mIsOrthoDragged;
 
     private int mActivePointerId = INVALID_POINTER;
 
     private int mTouchSlop;
 
     private float mLastMotionCoord;
+    private float mLastOrthoCoord;
 
     public Gallery(Context context) {
         this(context, null);
@@ -676,7 +678,7 @@
      *            This will either be the left or right edge of the view,
      *            depending on the fromLeft paramter
      * @param fromLeft
-     *            Are we posiitoning views based on the left edge? (i.e.,
+     *            Are we positioning views based on the left edge? (i.e.,
      *            building from left to right)?
      * @return A view that has been added to the gallery
      */
@@ -769,7 +771,7 @@
      *            Child to place
      * @return Where the top of the child should be
      */
-    private int calculateTop(View child, boolean duringLayout) {
+    protected int calculateTop(View child, boolean duringLayout) {
         int myHeight = mHorizontal ? (duringLayout ? getMeasuredHeight()
                 : getHeight()) : (duringLayout ? getMeasuredWidth()
                 : getWidth());
@@ -805,6 +807,9 @@
         if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) {
             return true;
         }
+        if ((action == MotionEvent.ACTION_MOVE) && (mIsOrthoDragged)) {
+            return true;
+        }
         switch (action & MotionEvent.ACTION_MASK) {
         case MotionEvent.ACTION_MOVE: {
             /*
@@ -821,10 +826,20 @@
             final int pointerIndex = ev.findPointerIndex(activePointerId);
             final float coord = mHorizontal ? ev.getX(pointerIndex) : ev
                     .getY(pointerIndex);
+
             final int diff = (int) Math.abs(coord - mLastMotionCoord);
             if (diff > mTouchSlop) {
                 mIsBeingDragged = true;
                 mLastMotionCoord = coord;
+            } else {
+                final float ocoord = mHorizontal ? ev.getY(pointerIndex)
+                        : ev.getX(pointerIndex);
+                if (Math.abs(ocoord - mLastOrthoCoord) > mTouchSlop) {
+                    mIsOrthoDragged = true;
+                    mLastOrthoCoord = ocoord;
+                }
+            }
+            if (mIsBeingDragged || mIsOrthoDragged) {
                 if (mParent != null)
                     mParent.requestDisallowInterceptTouchEvent(true);
             }
@@ -844,6 +859,9 @@
              * flinged.
              */
             mIsBeingDragged = !mFlingRunnable.mScroller.isFinished();
+            mIsOrthoDragged = false;
+            final float ocoord = mHorizontal ? ev.getY() : ev.getX();
+            mLastOrthoCoord = ocoord;
             mGestureDetector.onTouchEvent(ev);
             break;
         }
@@ -851,20 +869,24 @@
         case MotionEvent.ACTION_UP:
             /* Release the drag */
             mIsBeingDragged = false;
+            mIsOrthoDragged = false;
             mActivePointerId = INVALID_POINTER;
             break;
         case MotionEvent.ACTION_POINTER_DOWN: {
             final int index = ev.getActionIndex();
             mLastMotionCoord = mHorizontal ? ev.getX(index) : ev.getY(index);
+            mLastOrthoCoord = mHorizontal ? ev.getY(index) : ev.getX(index);
             mActivePointerId = ev.getPointerId(index);
             break;
         }
         case MotionEvent.ACTION_POINTER_UP:
-            mLastMotionCoord = ev.getX(ev.findPointerIndex(mActivePointerId));
+            mLastMotionCoord = mHorizontal ? ev.getX(ev.findPointerIndex(mActivePointerId))
+                    : ev.getY(ev.findPointerIndex(mActivePointerId));
+            mLastOrthoCoord = mHorizontal ? ev.getY(ev.findPointerIndex(mActivePointerId))
+                    : ev.getX(ev.findPointerIndex(mActivePointerId));
             break;
         }
-
-        return mIsBeingDragged;
+        return mIsBeingDragged || mIsOrthoDragged;
     }
 
     @Override
@@ -874,7 +896,7 @@
         int action = event.getAction();
         if (action == MotionEvent.ACTION_UP) {
             // Helper method for lifted finger
-            onUp();
+            onUp(mDownTouchView);
         } else if (action == MotionEvent.ACTION_CANCEL) {
             onCancel();
         }
@@ -902,6 +924,10 @@
             if (!mSuppressSelectionChanged)
                 mSuppressSelectionChanged = true;
         }
+        if (isOrthoMove(velocityX, velocityY)) {
+            onOrthoFling(mDownTouchView, e1, e2, mHorizontal ? velocityY : velocityX);
+            return true;
+        }
         mFlingRunnable.startUsingVelocity(mHorizontal ? (int) -velocityX
                 : (int) -velocityY);
         return true;
@@ -912,30 +938,43 @@
         if (localLOGV)
             Log.v(TAG, String.valueOf(e2.getX() - e1.getX()));
         mParent.requestDisallowInterceptTouchEvent(true);
-        if (!mShouldCallbackDuringFling) {
-            if (mIsFirstScroll) {
-                if (!mSuppressSelectionChanged)
-                    mSuppressSelectionChanged = true;
-                postDelayed(mDisableSuppressSelectionChangedRunnable,
-                        SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT);
+        if (mIsOrthoDragged && isOrthoMove(distanceX, distanceY)) {
+            onOrthoDrag(mDownTouchView, e1, e2, mHorizontal ? distanceY : distanceX);
+        } else if (mIsBeingDragged) {
+            if (!mShouldCallbackDuringFling) {
+                if (mIsFirstScroll) {
+                    if (!mSuppressSelectionChanged) {
+                        mSuppressSelectionChanged = true;
+                    }
+                    postDelayed(mDisableSuppressSelectionChangedRunnable,
+                            SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT);
+                }
+            } else {
+                if (mSuppressSelectionChanged) {
+                    mSuppressSelectionChanged = false;
+                }
             }
-        } else {
-            if (mSuppressSelectionChanged)
-                mSuppressSelectionChanged = false;
-        }
-        trackMotionScroll(mHorizontal ? -1 * (int) distanceX : -1
-                * (int) distanceY);
+            trackMotionScroll(mHorizontal ? -1 * (int) distanceX : -1
+                                            * (int) distanceY);
 
-        mIsFirstScroll = false;
+            mIsFirstScroll = false;
+        }
         return true;
     }
 
+    protected void onOrthoDrag(View draggedView, MotionEvent down,
+            MotionEvent move, float distance) {
+    }
+
+    protected void onOrthoFling(View draggedView, MotionEvent down,
+            MotionEvent move, float velocity) {
+    }
+
     public boolean onDown(MotionEvent e) {
         mFlingRunnable.stop(false);
         mDownTouchPosition = pointToPosition((int) e.getX(), (int) e.getY());
         if (mDownTouchPosition >= 0) {
             mDownTouchView = getChildAt(mDownTouchPosition - mFirstPosition);
-            mDownTouchView.setPressed(true);
         }
         // Reset the multiple-scroll tracking state
         mIsFirstScroll = true;
@@ -946,18 +985,23 @@
     /**
      * Called when a touch event's action is MotionEvent.ACTION_UP.
      */
-    void onUp() {
+    protected void onUp(View downView) {
         if (mFlingRunnable.mScroller.isFinished()) {
             scrollIntoSlots();
         }
         dispatchUnpress();
     }
 
+    private boolean isOrthoMove(float moveX, float moveY) {
+        return mHorizontal && Math.abs(moveY) > Math.abs(moveX)
+                || !mHorizontal && Math.abs(moveX) > Math.abs(moveY);
+    }
+
     /**
      * Called when a touch event's action is MotionEvent.ACTION_CANCEL.
      */
     void onCancel() {
-        onUp();
+        onUp(mDownTouchView);
     }
 
     public void onLongPress(MotionEvent e) {
@@ -986,9 +1030,6 @@
 
     @Override
     protected void dispatchSetPressed(boolean pressed) {
-        if (mSelectedChild != null) {
-            mSelectedChild.setPressed(pressed);
-        }
     }
 
     @Override
@@ -1063,7 +1104,7 @@
             long itemId) {
     }
 
-    boolean movePrevious() {
+    protected boolean movePrevious() {
         if (mItemCount > 0 && mSelectedPosition > 0) {
             scrollToChild(mSelectedPosition - mFirstPosition - 1);
             return true;