Move Right-click handling into UserInputHandler.
am: dbcccd65da

Change-Id: Ib28acfd62081f74d92c2a45b780f0b51763867ea
diff --git a/src/com/android/documentsui/dirlist/ListeningGestureDetector.java b/src/com/android/documentsui/dirlist/ListeningGestureDetector.java
index 85ff6ed..7a23d00 100644
--- a/src/com/android/documentsui/dirlist/ListeningGestureDetector.java
+++ b/src/com/android/documentsui/dirlist/ListeningGestureDetector.java
@@ -46,20 +46,13 @@
 
     @Override
     public boolean onInterceptTouchEvent(RecyclerView rv, MotionEvent e) {
-        // TODO: If possible, move this into UserInputHandler.
-        if (e.getAction() == MotionEvent.ACTION_DOWN && Events.isMouseEvent(e)) {
-            mInputHandler.setLastButtonState(e.getButtonState());
-        }
-
         // Detect drag events. When a drag is detected, intercept the rest of the gesture.
         View itemView = rv.findChildViewUnder(e.getX(), e.getY());
         if (itemView != null && mDragHelper.onTouch(itemView,  e)) {
             return true;
         }
         // Forward unhandled events to the GestureDetector.
-        onTouchEvent(e);
-
-        return false;
+        return onTouchEvent(e);
     }
 
     @Override
@@ -79,7 +72,7 @@
     @Override
     public boolean onTouch(View v, MotionEvent event) {
         if (event.getButtonState() == MotionEvent.BUTTON_SECONDARY) {
-            return mInputHandler.onSingleRightClickUp(event);
+            return mInputHandler.onRightClick(event);
         }
         return false;
     }
diff --git a/src/com/android/documentsui/dirlist/UserInputHandler.java b/src/com/android/documentsui/dirlist/UserInputHandler.java
index bd0cbf1..7324695 100644
--- a/src/com/android/documentsui/dirlist/UserInputHandler.java
+++ b/src/com/android/documentsui/dirlist/UserInputHandler.java
@@ -76,6 +76,20 @@
     }
 
     @Override
+    public boolean onDown(MotionEvent e) {
+        try (T event = mEventConverter.apply(e)) {
+            return onDown(event);
+        }
+    }
+
+    @VisibleForTesting
+    boolean onDown(T event) {
+        return event.isMouseEvent()
+                ? mMouseDelegate.onDown(event)
+                : mTouchDelegate.onDown(event);
+    }
+
+    @Override
     public boolean onSingleTapUp(MotionEvent e) {
         try (T event = mEventConverter.apply(e)) {
             return onSingleTapUp(event);
@@ -132,9 +146,12 @@
         mTouchDelegate.onLongPress(event);
     }
 
-    public boolean onSingleRightClickUp(MotionEvent e) {
+    // Only events from RecyclerView are fed into UserInputHandler#onDown.
+    // ListeningGestureDetector#onTouch directly calls this method to support context menu in empty
+    // view
+    boolean onRightClick(MotionEvent e) {
         try (T event = mEventConverter.apply(e)) {
-            return mMouseDelegate.onSingleRightClickUp(event);
+            return mMouseDelegate.onRightClick(event);
         }
     }
 
@@ -143,11 +160,6 @@
         return mKeyListener.onKey(doc, keyCode, event);
     }
 
-    // TODO: Isolate this hack...see if we can't get this solved at the platform level.
-    public void setLastButtonState(int state) {
-        mMouseDelegate.setLastButtonState(state);
-    }
-
     private boolean activateDocument(DocumentDetails doc) {
         return mActivateHandler.accept(doc);
     }
@@ -169,6 +181,10 @@
 
     private final class TouchInputDelegate {
 
+        boolean onDown(T event) {
+            return false;
+        }
+
         boolean onSingleTapUp(T event) {
             if (!event.isOverItem()) {
                 if (DEBUG) Log.d(TAG, "Tap on non-item. Clearing selection.");
@@ -220,25 +236,28 @@
     }
 
     private final class MouseInputDelegate {
-
-        // From the RecyclerView, we get two events sent to
-        // ListeningGestureDetector#onInterceptTouchEvent on a mouse click; we first get an
-        // ACTION_DOWN Event for clicking on the mouse, and then an ACTION_UP event from releasing
-        // the mouse click. ACTION_UP event doesn't have information regarding the button (primary
-        // vs. secondary), so we have to save that somewhere first from ACTION_DOWN, and then reuse
-        // it later. The ACTION_DOWN event doesn't get forwarded to UserInputListener,
-        // so we have open up a public set method to set it.
-        private int mLastButtonState = -1;
-
-        // true when the previous event has consumed a right click motion event
-        private boolean mAteRightClick;
-
         // The event has been handled in onSingleTapUp
         private boolean mHandledTapUp;
+        // true when the previous event has consumed a right click motion event
+        private boolean mHandledOnDown;
+
+        boolean onDown(T event) {
+            if (event.isSecondaryButtonPressed()) {
+                assert(!mHandledOnDown);
+                mHandledOnDown = true;
+                return onRightClick(event);
+            }
+            return false;
+        }
 
         boolean onSingleTapUp(T event) {
-            if (eatRightClick()) {
-                return onSingleRightClickUp(event);
+
+            // See b/27377794. Since we don't get a button state back from UP events, we have to
+            // explicitly save this state to know whether something was previously handled by
+            // DOWN events or not.
+            if (mHandledOnDown) {
+                mHandledOnDown = false;
+                return false;
             }
 
             if (!event.isOverItem()) {
@@ -272,10 +291,6 @@
         }
 
         boolean onSingleTapConfirmed(T event) {
-            if (mAteRightClick) {
-                mAteRightClick = false;
-                return false;
-            }
             if (mHandledTapUp) {
                 mHandledTapUp = false;
                 return false;
@@ -316,23 +331,9 @@
             }
         }
 
-        private boolean onSingleRightClickUp(T event) {
+        private boolean onRightClick(T event) {
             return mRightClickHandler.apply(event);
         }
-
-        // hack alert from here through end of class.
-        private void setLastButtonState(int state) {
-            mLastButtonState = state;
-        }
-
-        private boolean eatRightClick() {
-            if (mLastButtonState == MotionEvent.BUTTON_SECONDARY) {
-                mLastButtonState = -1;
-                mAteRightClick = true;
-                return true;
-            }
-            return false;
-        }
     }
 
     private final class KeyInputHandler {
diff --git a/tests/src/com/android/documentsui/TestInputEvent.java b/tests/src/com/android/documentsui/TestInputEvent.java
index e6936d6..5cd6514 100644
--- a/tests/src/com/android/documentsui/TestInputEvent.java
+++ b/tests/src/com/android/documentsui/TestInputEvent.java
@@ -105,6 +105,13 @@
         return e;
     }
 
+    public static TestInputEvent rightClick(int position) {
+        TestInputEvent e = new TestInputEvent(position);
+        e.mouseEvent = true;
+        e.secondaryButtonPressed = true;
+        return e;
+    }
+
     public static TestInputEvent shiftClick(int position) {
         TestInputEvent e = new TestInputEvent(position);
         e.mouseEvent = true;
diff --git a/tests/src/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java b/tests/src/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java
index 4e7997f..c459830 100644
--- a/tests/src/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java
+++ b/tests/src/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java
@@ -87,6 +87,12 @@
     }
 
     @Test
+    public void testRightClickDown_StartsContextMenu() {
+        mInputHandler.onDown(mEvent.secondary().build());
+        mRightClickHandler.assertLastArgument(mEvent.secondary().build());
+    }
+
+    @Test
     public void testUnconfirmedClick_AddsToExistingSelection() {
         mInputHandler.onSingleTapConfirmed(mEvent.at(7).build());
 
diff --git a/tests/src/com/android/documentsui/testing/TestEvent.java b/tests/src/com/android/documentsui/testing/TestEvent.java
index e59a603..f56118e 100644
--- a/tests/src/com/android/documentsui/testing/TestEvent.java
+++ b/tests/src/com/android/documentsui/testing/TestEvent.java
@@ -125,6 +125,11 @@
             return this;
         }
 
+        public Builder secondary() {
+            mState.secondaryButtonPressed = true;
+            return this;
+        }
+
         public TestEvent build() {
             // Return a copy, so nobody can mess w/ our internal state.
             TestEvent e = new TestEvent();
@@ -132,6 +137,7 @@
             e.modelId = mState.modelId;
             e.shiftKeyDow = mState.shiftKeyDow;
             e.mouseEvent = mState.mouseEvent;
+            e.secondaryButtonPressed = mState.secondaryButtonPressed;
             return e;
         }
     }