Merge "Swipe to veto in notifications."
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java
index 24eee27..b5ea7b2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/NotificationRowLayout.java
@@ -20,6 +20,7 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.AnimatorSet;
 import android.animation.ObjectAnimator;
+import android.animation.TimeAnimator;
 import android.animation.ValueAnimator;
 import android.content.Context;
 import android.content.res.Resources;
@@ -30,6 +31,8 @@
 import android.util.AttributeSet;
 import android.util.Slog;
 import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.VelocityTracker;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.animation.AccelerateInterpolator;
@@ -45,10 +48,15 @@
 public class NotificationRowLayout extends ViewGroup {
     private static final String TAG = "NotificationRowLayout";
     private static final boolean DEBUG = false;
+    private static final boolean SLOW_ANIMATIONS = false; // DEBUG;
 
     private static final boolean ANIMATE_LAYOUT = true;
 
-    private static final int ANIM_LEN = DEBUG ? 5000 : 250;
+    private static final int APPEAR_ANIM_LEN = SLOW_ANIMATIONS ? 5000 : 250;
+    private static final int DISAPPEAR_ANIM_LEN = APPEAR_ANIM_LEN;
+    private static final int SNAP_ANIM_LEN = SLOW_ANIMATIONS ? 1000 : 250;
+
+    private static final float SWIPE_ESCAPE_VELOCITY = 1500f;
 
     Rect mTmpRect = new Rect();
     int mNumRows = 0;
@@ -58,6 +66,11 @@
     HashSet<View> mAppearingViews = new HashSet<View>();
     HashSet<View> mDisappearingViews = new HashSet<View>();
 
+    VelocityTracker mVT;
+    float mInitialTouchX, mInitialTouchY;
+    View mSlidingChild = null;
+    float mLiftoffVelocity;
+
     public NotificationRowLayout(Context context, AttributeSet attrs) {
         this(context, attrs, 0);
     }
@@ -65,6 +78,8 @@
     public NotificationRowLayout(Context context, AttributeSet attrs, int defStyle) {
         super(context, attrs, defStyle);
 
+        mVT = VelocityTracker.obtain();
+
         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.NotificationRowLayout,
                 defStyle, 0);
         mRowHeight = a.getDimensionPixelSize(R.styleable.NotificationRowLayout_rowHeight, 0);
@@ -89,6 +104,92 @@
 
     }
 
+    // Swipey code
+    @Override
+    public boolean onInterceptTouchEvent(MotionEvent ev) {
+        final int action = ev.getAction();
+//        if (DEBUG) Slog.d(TAG, "intercepting touch event: " + ev);
+        switch (action) {
+            case MotionEvent.ACTION_DOWN:
+                mVT.clear();
+                mVT.addMovement(ev);
+                mInitialTouchX = ev.getX();
+                mInitialTouchY = ev.getY();
+                mSlidingChild = null;
+                break;
+            case MotionEvent.ACTION_MOVE:
+                mVT.addMovement(ev);
+                if (mSlidingChild == null) {
+                    if (Math.abs(ev.getX() - mInitialTouchX) > 4) { // slide slop
+
+                        // find the view under the pointer, accounting for GONE views
+                        final int count = getChildCount();
+                        int y = 0;
+                        int childIdx = 0;
+                        for (; childIdx < count; childIdx++) {
+                            mSlidingChild = getChildAt(childIdx);
+                            if (mSlidingChild.getVisibility() == GONE) {
+                                continue;
+                            }
+                            y += mRowHeight;
+                            if (mInitialTouchY < y) break;
+                        }
+
+                        mInitialTouchX -= mSlidingChild.getTranslationX();
+                        mSlidingChild.animate().cancel();
+
+                        if (DEBUG) {
+                            Slog.d(TAG, String.format(
+                                "now sliding child %d: %s (touchY=%.1f, rowHeight=%d, count=%d)",
+                                childIdx, mSlidingChild, mInitialTouchY, mRowHeight, count));
+                        }
+                    }
+                }
+                break;
+        }
+        return mSlidingChild != null;
+    }
+    @Override
+    public boolean onTouchEvent(MotionEvent ev) {
+        final int action = ev.getAction();
+//        if (DEBUG) Slog.d(TAG, "touch event: " + ev + " sliding: " + mSlidingChild);
+        if (mSlidingChild != null) {
+            switch (action) {
+                case MotionEvent.ACTION_OUTSIDE:
+                case MotionEvent.ACTION_MOVE:
+                    mVT.addMovement(ev);
+
+                    mSlidingChild.setTranslationX(ev.getX() - mInitialTouchX);
+                    break;
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    mVT.addMovement(ev);
+                    mVT.computeCurrentVelocity(1000 /* px/sec */);
+                    if (DEBUG) Slog.d(TAG, "exit velocity: " + mVT.getXVelocity());
+                    boolean restore = true;
+                    mLiftoffVelocity = mVT.getXVelocity();
+                    if (Math.abs(mLiftoffVelocity) > SWIPE_ESCAPE_VELOCITY) {
+                        // flingadingy
+
+                        View veto = mSlidingChild.findViewById(R.id.veto);
+                        if (veto != null && veto.getVisibility() == View.VISIBLE) {
+                            veto.performClick();
+                            restore = false;
+                        }
+                    }
+                    if (restore) {
+                        // snappity
+                        mSlidingChild.animate().translationX(0)
+                            .setDuration(SNAP_ANIM_LEN)
+                            .start();
+                    }
+                    break;
+            }
+            return true;
+        }
+        return false;
+    }
+
     //**
     @Override
     public void addView(View child, int index, LayoutParams params) {
@@ -105,7 +206,7 @@
                     ObjectAnimator.ofFloat(child, "alpha", 0f, 1f)
 //                    ,ObjectAnimator.ofFloat(child, "scaleY", 0f, 1f)
             );
-            a.setDuration(ANIM_LEN);
+            a.setDuration(APPEAR_ANIM_LEN);
             a.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
@@ -127,21 +228,36 @@
             mDisappearingViews.add(child);
 
             child.setPivotY(0);
-            AnimatorSet a = new AnimatorSet();
-            a.playTogether(
-                    ObjectAnimator.ofFloat(child, "alpha", 0f)
-//                    ,ObjectAnimator.ofFloat(child, "scaleY", 0f)
-                    ,ObjectAnimator.ofFloat(child, "translationX", 300f)
-            );
-            a.setDuration(ANIM_LEN);
-            a.addListener(new AnimatorListenerAdapter() {
+
+            final float velocity = (mSlidingChild == child) 
+                    ? mLiftoffVelocity : SWIPE_ESCAPE_VELOCITY;
+            final TimeAnimator zoom = new TimeAnimator();
+            zoom.setTimeListener(new TimeAnimator.TimeListener() {
+                @Override
+                public void onTimeUpdate(TimeAnimator animation, long totalTime, long deltaTime) {
+                    childF.setTranslationX(childF.getTranslationX() + deltaTime / 1000f * velocity);
+                }
+            });
+
+            final ObjectAnimator alphaFade = ObjectAnimator.ofFloat(child, "alpha", 0f);
+            alphaFade.addListener(new AnimatorListenerAdapter() {
                 @Override
                 public void onAnimationEnd(Animator animation) {
+                    zoom.cancel(); // it won't end on its own
+                    if (DEBUG) Slog.d(TAG, "actually removing child: " + childF);
                     NotificationRowLayout.super.removeView(childF);
                     childF.setAlpha(1f);
                     mDisappearingViews.remove(childF);
                 }
             });
+
+            AnimatorSet a = new AnimatorSet();
+            a.playTogether(alphaFade, zoom);
+                    
+//                    ,ObjectAnimator.ofFloat(child, "scaleY", 0f)
+//                    ,ObjectAnimator.ofFloat(child, "translationX", child.getTranslationX() + 300f)
+
+            a.setDuration(DISAPPEAR_ANIM_LEN);
             a.start();
             requestLayout(); // start the container animation
         } else {
@@ -160,8 +276,8 @@
     public void onDraw(android.graphics.Canvas c) {
         super.onDraw(c);
         if (DEBUG) {
-            Slog.d(TAG, "onDraw: canvas height: " + c.getHeight() + "px; measured height: "
-                    + getMeasuredHeight() + "px");
+            //Slog.d(TAG, "onDraw: canvas height: " + c.getHeight() + "px; measured height: "
+            //        + getMeasuredHeight() + "px");
             c.save();
             c.clipRect(6, 6, c.getWidth() - 6, getMeasuredHeight() - 6,
                     android.graphics.Region.Op.DIFFERENCE);
@@ -199,7 +315,7 @@
 
             if (ANIMATE_LAYOUT && isShown()) {
                 ObjectAnimator.ofInt(this, "forcedHeight", computedHeight)
-                    .setDuration(ANIM_LEN)
+                    .setDuration(APPEAR_ANIM_LEN)
                     .start();
             } else {
                 setForcedHeight(computedHeight);
@@ -230,7 +346,7 @@
         final int width = right - left;
         final int height = bottom - top;
 
-        if (DEBUG) Slog.d(TAG, "onLayout: height=" + height);
+        //if (DEBUG) Slog.d(TAG, "onLayout: height=" + height);
 
         final int count = getChildCount();
         int y = 0;
@@ -252,7 +368,7 @@
     }
 
     public void setForcedHeight(int h) {
-        if (DEBUG) Slog.d(TAG, "forcedHeight: " + h);
+        //if (DEBUG) Slog.d(TAG, "forcedHeight: " + h);
         if (h != mHeight) {
             mHeight = h;
             requestLayout();