DragScrollListener test, from ag/1247908.

Change-Id: Ifd550281094fd12395333ae2071742d5893cd9f7
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index db19881..e6e1048 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -215,8 +215,9 @@
 
         mRecView.setItemAnimator(new DirectoryItemAnimator(getActivity()));
 
+        final int edgeHeight = (int) getResources().getDimension(R.dimen.autoscroll_edge_height);
         mOnDragListener = DragScrollListener.create(
-                getActivity(), new DirectoryDragListener(this), mRecView);
+                edgeHeight, new DirectoryDragListener(this), mRecView);
 
         // Make the recycler and the empty views responsive to drop events.
         mRecView.setOnDragListener(mOnDragListener);
diff --git a/src/com/android/documentsui/dirlist/DragScrollListener.java b/src/com/android/documentsui/dirlist/DragScrollListener.java
index 898a4a2..4518033 100644
--- a/src/com/android/documentsui/dirlist/DragScrollListener.java
+++ b/src/com/android/documentsui/dirlist/DragScrollListener.java
@@ -16,8 +16,8 @@
 
 package com.android.documentsui.dirlist;
 
-import android.content.Context;
 import android.graphics.Point;
+import android.support.annotation.VisibleForTesting;
 import android.view.DragEvent;
 import android.view.View;
 import android.view.View.OnDragListener;
@@ -26,7 +26,6 @@
 import com.android.documentsui.ItemDragListener.DragHost;
 import com.android.documentsui.dirlist.ViewAutoScroller.ScrollActionDelegate;
 import com.android.documentsui.dirlist.ViewAutoScroller.ScrollDistanceDelegate;
-import com.android.documentsui.R;
 
 import java.util.function.BooleanSupplier;
 import java.util.function.IntSupplier;
@@ -49,16 +48,16 @@
     private boolean mDragHappening;
     private @Nullable Point mCurrentPosition;
 
-    private DragScrollListener(
-            Context context,
+    @VisibleForTesting
+    DragScrollListener(
+            int autoScrollEdgeHeight,
             ItemDragListener<? extends DragHost> dragHandler,
             IntSupplier heightSupplier,
             BooleanSupplier scrollUpSupplier,
             BooleanSupplier scrollDownSupplier,
             ViewAutoScroller.ScrollActionDelegate actionDelegate) {
         mDragHandler = dragHandler;
-        mAutoScrollEdgeHeight = (int) context.getResources()
-                .getDimension(R.dimen.autoscroll_edge_height);
+        mAutoScrollEdgeHeight = autoScrollEdgeHeight;
         mHeight = heightSupplier;
         mCanScrollUp = scrollUpSupplier;
         mCanScrollDown = scrollDownSupplier;
@@ -85,7 +84,9 @@
     }
 
     static DragScrollListener create(
-            Context context, ItemDragListener<? extends DragHost> dragHandler, View scrollView) {
+            int autoScrollEdgeHeight,
+            ItemDragListener<? extends DragHost> dragHandler,
+            View scrollView) {
         ScrollActionDelegate actionDelegate = new ScrollActionDelegate() {
             @Override
             public void scrollBy(int dy) {
@@ -104,7 +105,7 @@
             }
         };
         DragScrollListener listener = new DragScrollListener(
-                context,
+                autoScrollEdgeHeight,
                 dragHandler,
                 scrollView::getHeight,
                 () -> {
diff --git a/tests/src/com/android/documentsui/dirlist/DragScrollListenerTest.java b/tests/src/com/android/documentsui/dirlist/DragScrollListenerTest.java
new file mode 100644
index 0000000..edc5537
--- /dev/null
+++ b/tests/src/com/android/documentsui/dirlist/DragScrollListenerTest.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.documentsui.dirlist;
+
+import static org.junit.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+import android.view.DragEvent;
+import android.view.View;
+
+import com.android.documentsui.ItemDragListener;
+import com.android.documentsui.dirlist.ViewAutoScroller.ScrollActionDelegate;
+import com.android.documentsui.testing.DragEvents;
+import com.android.documentsui.testing.TestTimer;
+import com.android.documentsui.testing.Views;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Timer;
+import java.util.function.IntConsumer;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DragScrollListenerTest {
+
+    private static final int VIEW_HEIGHT = 100;
+    private static final int EDGE_HEIGHT = 10;
+
+    private View mTestView;
+    private TestDragHost mTestDragHost;
+    private TestTimer mTestTimer;
+    private TestDragHandler mDragHandler;
+    private TestScrollActionDelegate mActionDelegate = new TestScrollActionDelegate();
+    private DragScrollListener mListener;
+    private boolean mCanScrollUp;
+    private boolean mCanScrollDown;
+
+    @Before
+    public void setUp() {
+        mTestView = Views.createTestView(0, 0);
+        mTestTimer = new TestTimer();
+        mTestDragHost = new TestDragHost();
+        mDragHandler = new TestDragHandler(mTestDragHost, mTestTimer);
+        mListener = new DragScrollListener(
+                EDGE_HEIGHT,
+                mDragHandler,
+                () -> {
+                    return VIEW_HEIGHT;
+                },
+                () -> {
+                    return mCanScrollUp;
+                },
+                () -> {
+                    return mCanScrollDown;
+                },
+                mActionDelegate);
+        mCanScrollUp = true;
+        mCanScrollDown = true;
+    }
+
+    @Test
+    public void testDragEvent_DelegateToHandler() {
+        triggerDragEvent(DragEvent.ACTION_DRAG_STARTED);
+        assertTrue(mDragHandler.mLastDropEvent.getAction() == DragEvent.ACTION_DRAG_STARTED);
+
+        triggerDragEvent(DragEvent.ACTION_DROP);
+        assertTrue(mDragHandler.mLastDropEvent.getAction() == DragEvent.ACTION_DROP);
+
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENDED);
+        assertTrue(mDragHandler.mLastDropEvent.getAction() == DragEvent.ACTION_DRAG_ENDED);
+
+        triggerDragEvent(DragEvent.ACTION_DRAG_EXITED);
+        assertTrue(mDragHandler.mLastDropEvent.getAction() == DragEvent.ACTION_DRAG_EXITED);
+    }
+
+    @Test
+    public void testDragLocationEvent_DelegateToHandler() {
+        triggerDragEvent(DragEvent.ACTION_DRAG_STARTED);
+
+        // Not in hotspot
+        triggerDragLocationEvent(0, 50);
+        assertTrue(mDragHandler.mLastDropEvent.getAction() == DragEvent.ACTION_DRAG_LOCATION);
+
+        // Can't scroll up
+        mCanScrollUp = false;
+        mCanScrollDown = true;
+        triggerDragLocationEvent(0, 5);
+        assertTrue(mDragHandler.mLastDropEvent.getAction() == DragEvent.ACTION_DRAG_LOCATION);
+
+        // Can't scroll Down
+        mCanScrollDown = false;
+        mCanScrollUp = true;
+        triggerDragLocationEvent(0, 95);
+        assertTrue(mDragHandler.mLastDropEvent.getAction() == DragEvent.ACTION_DRAG_LOCATION);
+
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENDED);
+    }
+
+    @Test
+    public void testDragEnterEvent_DelegateToHandler() {
+        triggerDragEvent(DragEvent.ACTION_DRAG_STARTED);
+
+        // Location Event always precedes Entered event
+        triggerDragLocationEvent(0, 50);
+        // If not in the hotspot, we don't want to trap the event
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENTERED);
+        assertTrue(mDragHandler.mLastDropEvent.getAction() == DragEvent.ACTION_DRAG_ENTERED);
+
+        // Can't scroll up
+        mCanScrollUp = false;
+        mCanScrollDown = true;
+        triggerDragLocationEvent(0, 5);
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENTERED);
+        assertTrue(mDragHandler.mLastDropEvent.getAction() == DragEvent.ACTION_DRAG_ENTERED);
+
+        // Can't scroll Down
+        mCanScrollDown = false;
+        mCanScrollUp = true;
+        triggerDragLocationEvent(0, 95);
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENTERED);
+        assertTrue(mDragHandler.mLastDropEvent.getAction() == DragEvent.ACTION_DRAG_ENTERED);
+
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENDED);
+    }
+
+    // Make sure given correct location/enter events, DragScrollListener handle them itself
+    @Test
+    public void testDragScrollEvent_Handled() {
+        triggerDragLocationEvent(0, 5);
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENTERED);
+        assertTrue(mDragHandler.mLastDropEvent == null);
+
+        triggerDragLocationEvent(0, 95);
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENTERED);
+        assertTrue(mDragHandler.mLastDropEvent == null);
+    }
+
+    // A correct Auto-scroll happens in the sequence of:
+    // Started -> LocationChanged -> Scroll -> Enter -> Exit
+    // This test to make sure scroll actually happens in the right direction given correct sequence
+    @Test
+    public void testActualDragScrolldEvents() {
+        triggerDragEvent(DragEvent.ACTION_DRAG_STARTED);
+
+        triggerDragLocationEvent(0, 5);
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENTERED);
+        assertTrue(mDragHandler.mLastDropEvent.getAction() == DragEvent.ACTION_DRAG_STARTED);
+        mActionDelegate.assertScrollNegative();
+
+        triggerDragLocationEvent(0, 95);
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENTERED);
+
+        triggerDragEvent(DragEvent.ACTION_DRAG_ENDED);
+        assertTrue(mDragHandler.mLastDropEvent.getAction() == DragEvent.ACTION_DRAG_ENDED);
+        mActionDelegate.assertScrollPositive();
+    }
+
+    protected boolean triggerDragEvent(int actionId) {
+        final DragEvent testEvent = DragEvents.createTestDragEvent(actionId);
+
+        return mListener.onDrag(mTestView, testEvent);
+    }
+
+    protected boolean triggerDragLocationEvent(float x, float y) {
+        final DragEvent testEvent = DragEvents.createTestLocationEvent(x, y);
+
+        return mListener.onDrag(mTestView, testEvent);
+    }
+
+    private static class TestDragHandler extends ItemDragListener<TestDragHost> {
+
+        private DragEvent mLastDropEvent;
+
+        protected TestDragHandler(TestDragHost dragHost, Timer timer) {
+            super(dragHost, timer);
+        }
+
+        @Override
+        public boolean onDrag(View v, DragEvent event) {
+            mLastDropEvent = event;
+            return true;
+        }
+    }
+
+    private static class TestDragHost implements ItemDragListener.DragHost {
+
+        @Override
+        public void setDropTargetHighlight(View v, boolean highlight) {
+        }
+
+        @Override
+        public void runOnUiThread(Runnable runnable) {
+        }
+
+        @Override
+        public void onViewHovered(View v) {
+        }
+    }
+
+    private class TestScrollActionDelegate implements ScrollActionDelegate {
+
+        private int mDy;
+
+        @Override
+        public void scrollBy(int dy) {
+            mDy = dy;
+        }
+
+        @Override
+        public void runAtNextFrame(Runnable r) {
+        }
+
+        @Override
+        public void removeCallback(Runnable r) {
+        }
+
+        public void assertScrollPositive() {
+            assertTrue(mDy > 0);
+        }
+
+        public void assertScrollNegative() {
+            assertTrue(mDy < 0);
+        }
+    };
+}
diff --git a/tests/src/com/android/documentsui/testing/Views.java b/tests/src/com/android/documentsui/testing/Views.java
index 52a9cbc..d2b920d 100644
--- a/tests/src/com/android/documentsui/testing/Views.java
+++ b/tests/src/com/android/documentsui/testing/Views.java
@@ -33,6 +33,17 @@
         return view;
     }
 
+    /*
+     * Dummy View object with (x, y) coordinates
+     */
+    public static View createTestView(float x, float y) {
+        View view = createTestView();
+        Mockito.when(view.getX()).thenReturn(x);
+        Mockito.when(view.getY()).thenReturn(y);
+
+        return view;
+    }
+
     public static void setBackground(View testView, Drawable background) {
         Mockito.when(testView.getBackground()).thenReturn(background);
     }