Move drag event to InputDispatcher (7/n)

This CL handle the special case while using a stylus device. During drag
and drop and the primary button was down, we will check the button being
lifted in the future, if that happens, we'll drop the item.

This includes the behavior:
-- Touch screen with stylus -> start drag drop with button pressed ->
   move and drag item -> drop item by releasing button.

We could still drop item when receiving ACTION_UP.

Bug: 19621008
Bug: 158242495
Test: atest inputflinger_tests
Change-Id: Ia84f4fd85367f96272af3a19c3db50c4216de1b0
diff --git a/services/inputflinger/dispatcher/Android.bp b/services/inputflinger/dispatcher/Android.bp
index 393f649..9750ef9 100644
--- a/services/inputflinger/dispatcher/Android.bp
+++ b/services/inputflinger/dispatcher/Android.bp
@@ -42,6 +42,7 @@
         "InputTarget.cpp",
         "Monitor.cpp",
         "TouchState.cpp",
+        "DragState.cpp",
     ],
 }
 
diff --git a/services/inputflinger/dispatcher/DragState.cpp b/services/inputflinger/dispatcher/DragState.cpp
new file mode 100644
index 0000000..2e2df43
--- /dev/null
+++ b/services/inputflinger/dispatcher/DragState.cpp
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#include "DragState.h"
+#include <android-base/stringprintf.h>
+#include <input/InputWindow.h>
+
+using android::InputWindowHandle;
+using android::base::StringPrintf;
+
+namespace android::inputdispatcher {
+
+void DragState::dump(std::string& dump, const char* prefix) {
+    dump += prefix + StringPrintf("Drag Window: %s\n", dragWindow->getName().c_str());
+    if (dragHoverWindowHandle) {
+        dump += prefix +
+                StringPrintf("Drag Hover Window: %s\n", dragHoverWindowHandle->getName().c_str());
+    }
+    dump += prefix + StringPrintf("isStartDrag: %s\n", isStartDrag ? "true" : "false");
+    dump += prefix +
+            StringPrintf("isStylusButtonDownAtStart: %s\n",
+                         isStylusButtonDownAtStart ? "true" : "false");
+}
+
+} // namespace android::inputdispatcher
\ No newline at end of file
diff --git a/services/inputflinger/dispatcher/DragState.h b/services/inputflinger/dispatcher/DragState.h
new file mode 100644
index 0000000..06453d8
--- /dev/null
+++ b/services/inputflinger/dispatcher/DragState.h
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+#ifndef _UI_INPUT_INPUTDISPATCHER_DRAGSTATE_H
+#define _UI_INPUT_INPUTDISPATCHER_DRAGSTATE_H
+
+#include <utils/RefBase.h>
+#include <string>
+
+namespace android {
+
+class InputWindowHandle;
+
+namespace inputdispatcher {
+struct DragState {
+    DragState(const sp<android::InputWindowHandle>& windowHandle) : dragWindow(windowHandle) {}
+    void dump(std::string& dump, const char* prefix = "");
+
+    // The window being dragged.
+    const sp<InputWindowHandle> dragWindow;
+    // The last drag hover window which could receive the drag event.
+    sp<InputWindowHandle> dragHoverWindowHandle;
+    // Indicates the if received first event to check for button state.
+    bool isStartDrag = false;
+    // Indicate if the stylus button is down at the start of the drag.
+    bool isStylusButtonDownAtStart = false;
+};
+
+} // namespace inputdispatcher
+} // namespace android
+
+#endif // _UI_INPUT_INPUTDISPATCHER_DRAGSTATE_H
\ No newline at end of file
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 397a9d7..6977396 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -961,7 +961,7 @@
     // Traverse windows from front to back to find touched window.
     const std::vector<sp<InputWindowHandle>>& windowHandles = getWindowHandlesLocked(displayId);
     for (const sp<InputWindowHandle>& windowHandle : windowHandles) {
-        if (ignoreDragWindow && haveSameToken(windowHandle, touchState->dragWindow)) {
+        if (ignoreDragWindow && haveSameToken(windowHandle, mDragState->dragWindow)) {
             continue;
         }
         const InputWindowInfo* windowInfo = windowHandle->getInfo();
@@ -2061,7 +2061,7 @@
             goto Failed;
         }
 
-        addDragEventLocked(entry, tempTouchState);
+        addDragEventLocked(entry);
 
         // Check whether touches should slip outside of the current foreground window.
         if (maskedAction == AMOTION_EVENT_ACTION_MOVE && entry.pointerCount == 1 &&
@@ -2318,39 +2318,61 @@
     return injectionResult;
 }
 
-void InputDispatcher::addDragEventLocked(const MotionEntry& entry, TouchState& state) {
-    if (entry.pointerCount != 1 || !state.dragWindow) {
+void InputDispatcher::finishDragAndDrop(int32_t displayId, float x, float y) {
+    const sp<InputWindowHandle> dropWindow =
+            findTouchedWindowAtLocked(displayId, x, y, nullptr /*touchState*/,
+                                      false /*addOutsideTargets*/, false /*addPortalWindows*/,
+                                      true /*ignoreDragWindow*/);
+    if (dropWindow) {
+        vec2 local = dropWindow->getInfo()->transform.transform(x, y);
+        notifyDropWindowLocked(dropWindow->getToken(), local.x, local.y);
+    }
+    mDragState.reset();
+}
+
+void InputDispatcher::addDragEventLocked(const MotionEntry& entry) {
+    if (entry.pointerCount != 1 || !mDragState) {
         return;
     }
 
+    if (!mDragState->isStartDrag) {
+        mDragState->isStartDrag = true;
+        mDragState->isStylusButtonDownAtStart =
+                (entry.buttonState & AMOTION_EVENT_BUTTON_STYLUS_PRIMARY) != 0;
+    }
+
     int32_t maskedAction = entry.action & AMOTION_EVENT_ACTION_MASK;
     int32_t x = static_cast<int32_t>(entry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_X));
     int32_t y = static_cast<int32_t>(entry.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_Y));
     if (maskedAction == AMOTION_EVENT_ACTION_MOVE) {
+        // Handle the special case : stylus button no longer pressed.
+        bool isStylusButtonDown = (entry.buttonState & AMOTION_EVENT_BUTTON_STYLUS_PRIMARY) != 0;
+        if (mDragState->isStylusButtonDownAtStart && !isStylusButtonDown) {
+            finishDragAndDrop(entry.displayId, x, y);
+            return;
+        }
+
         const sp<InputWindowHandle> hoverWindowHandle =
-                findTouchedWindowAtLocked(entry.displayId, x, y, &state,
+                findTouchedWindowAtLocked(entry.displayId, x, y, nullptr /*touchState*/,
                                           false /*addOutsideTargets*/, false /*addPortalWindows*/,
                                           true /*ignoreDragWindow*/);
         // enqueue drag exit if needed.
-        if (hoverWindowHandle != state.dragHoverWindowHandle &&
-            !haveSameToken(hoverWindowHandle, state.dragHoverWindowHandle)) {
-            if (state.dragHoverWindowHandle != nullptr) {
-                enqueueDragEventLocked(state.dragHoverWindowHandle, true /*isExiting*/, entry);
+        if (hoverWindowHandle != mDragState->dragHoverWindowHandle &&
+            !haveSameToken(hoverWindowHandle, mDragState->dragHoverWindowHandle)) {
+            if (mDragState->dragHoverWindowHandle != nullptr) {
+                enqueueDragEventLocked(mDragState->dragHoverWindowHandle, true /*isExiting*/,
+                                       entry);
             }
-            state.dragHoverWindowHandle = hoverWindowHandle;
+            mDragState->dragHoverWindowHandle = hoverWindowHandle;
         }
         // enqueue drag location if needed.
         if (hoverWindowHandle != nullptr) {
             enqueueDragEventLocked(hoverWindowHandle, false /*isExiting*/, entry);
         }
-    } else if (maskedAction == AMOTION_EVENT_ACTION_UP ||
-               maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
-        if (state.dragHoverWindowHandle && maskedAction == AMOTION_EVENT_ACTION_UP) {
-            vec2 local = state.dragHoverWindowHandle->getInfo()->transform.transform(x, y);
-            notifyDropWindowLocked(state.dragHoverWindowHandle->getToken(), local.x, local.y);
-        }
-        state.dragWindow = nullptr;
-        state.dragHoverWindowHandle = nullptr;
+    } else if (maskedAction == AMOTION_EVENT_ACTION_UP) {
+        finishDragAndDrop(entry.displayId, x, y);
+    } else if (maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
+        mDragState.reset();
     }
 }
 
@@ -4445,13 +4467,12 @@
             }
         }
 
-        // If drag window is gone, it would receive a cancel event and broadcast the DRAG_END. we
+        // If drag window is gone, it would receive a cancel event and broadcast the DRAG_END. We
         // could just clear the state here.
-        if (state.dragWindow &&
-            std::find(windowHandles.begin(), windowHandles.end(), state.dragWindow) ==
+        if (mDragState &&
+            std::find(windowHandles.begin(), windowHandles.end(), mDragState->dragWindow) ==
                     windowHandles.end()) {
-            state.dragWindow = nullptr;
-            state.dragHoverWindowHandle = nullptr;
+            mDragState.reset();
         }
     }
 
@@ -4690,7 +4711,7 @@
 
                     // Store the dragging window.
                     if (isDragDrop) {
-                        state.dragWindow = toWindowHandle;
+                        mDragState = std::make_unique<DragState>(toWindowHandle);
                     }
 
                     found = true;
@@ -4833,6 +4854,11 @@
         dump += INDENT "TouchStates: <no displays touched>\n";
     }
 
+    if (mDragState) {
+        dump += StringPrintf(INDENT "DragState:\n");
+        mDragState->dump(dump, INDENT2);
+    }
+
     if (!mWindowHandlesByDisplay.empty()) {
         for (auto& it : mWindowHandlesByDisplay) {
             const std::vector<sp<InputWindowHandle>> windowHandles = it.second;
diff --git a/services/inputflinger/dispatcher/InputDispatcher.h b/services/inputflinger/dispatcher/InputDispatcher.h
index 593ec23..5708fac 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.h
+++ b/services/inputflinger/dispatcher/InputDispatcher.h
@@ -19,6 +19,7 @@
 
 #include "AnrTracker.h"
 #include "CancelationOptions.h"
+#include "DragState.h"
 #include "Entry.h"
 #include "FocusResolver.h"
 #include "InjectionState.h"
@@ -340,6 +341,7 @@
             REQUIRES(mLock);
 
     std::unordered_map<int32_t, TouchState> mTouchStatesByDisplay GUARDED_BY(mLock);
+    std::unique_ptr<DragState> mDragState GUARDED_BY(mLock);
 
     // Focused applications.
     std::unordered_map<int32_t, std::shared_ptr<InputApplicationHandle>>
@@ -499,7 +501,8 @@
                                   const InjectionState* injectionState);
     // Enqueue a drag event if needed, and update the touch state.
     // Uses findTouchedWindowTargetsLocked to make the decision
-    void addDragEventLocked(const MotionEntry& entry, TouchState& state) REQUIRES(mLock);
+    void addDragEventLocked(const MotionEntry& entry) REQUIRES(mLock);
+    void finishDragAndDrop(int32_t displayId, float x, float y) REQUIRES(mLock);
 
     struct TouchOcclusionInfo {
         bool hasBlockingOcclusion;
diff --git a/services/inputflinger/dispatcher/TouchState.cpp b/services/inputflinger/dispatcher/TouchState.cpp
index 4165f49..81b3cf0 100644
--- a/services/inputflinger/dispatcher/TouchState.cpp
+++ b/services/inputflinger/dispatcher/TouchState.cpp
@@ -49,8 +49,6 @@
     windows = other.windows;
     portalWindows = other.portalWindows;
     gestureMonitors = other.gestureMonitors;
-    dragHoverWindowHandle = other.dragHoverWindowHandle;
-    dragWindow = other.dragWindow;
 }
 
 void TouchState::addOrUpdateWindow(const sp<InputWindowHandle>& windowHandle, int32_t targetFlags,
diff --git a/services/inputflinger/dispatcher/TouchState.h b/services/inputflinger/dispatcher/TouchState.h
index d7a561c..623c6a8 100644
--- a/services/inputflinger/dispatcher/TouchState.h
+++ b/services/inputflinger/dispatcher/TouchState.h
@@ -41,11 +41,6 @@
 
     std::vector<TouchedMonitor> gestureMonitors;
 
-    // The last drag hover window which could receive the drag event.
-    sp<InputWindowHandle> dragHoverWindowHandle;
-    // The window being dragged.
-    sp<InputWindowHandle> dragWindow;
-
     TouchState();
     ~TouchState();
     void reset();
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index cedda6e..485fc99 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -1165,8 +1165,8 @@
         return *this;
     }
 
-    MotionEventBuilder& buttonState(int32_t actionButton) {
-        mActionButton = actionButton;
+    MotionEventBuilder& buttonState(int32_t buttonState) {
+        mButtonState = buttonState;
         return *this;
     }
 
@@ -4761,6 +4761,32 @@
         mWindow->consumeMotionCancel();
         mDragWindow->consumeMotionDown();
     }
+
+    // Start performing drag, we will create a drag window and transfer touch to it.
+    void performStylusDrag() {
+        ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+                  injectMotionEvent(mDispatcher,
+                                    MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN,
+                                                       AINPUT_SOURCE_STYLUS)
+                                            .buttonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)
+                                            .pointer(PointerBuilder(0,
+                                                                    AMOTION_EVENT_TOOL_TYPE_STYLUS)
+                                                             .x(50)
+                                                             .y(50))
+                                            .build()));
+        mWindow->consumeMotionDown(ADISPLAY_ID_DEFAULT);
+
+        // The drag window covers the entire display
+        mDragWindow = new FakeWindowHandle(mApp, mDispatcher, "DragWindow", ADISPLAY_ID_DEFAULT);
+        mDispatcher->setInputWindows(
+                {{ADISPLAY_ID_DEFAULT, {mDragWindow, mWindow, mSecondWindow}}});
+
+        // Transfer touch focus to the drag window
+        mDispatcher->transferTouchFocus(mWindow->getToken(), mDragWindow->getToken(),
+                                        true /* isDragDrop */);
+        mWindow->consumeMotionCancel();
+        mDragWindow->consumeMotionDown();
+    }
 };
 
 TEST_F(InputDispatcherDragTests, DragEnterAndDragExit) {
@@ -4833,4 +4859,51 @@
     mSecondWindow->assertNoEvents();
 }
 
+TEST_F(InputDispatcherDragTests, StylusDragAndDrop) {
+    performStylusDrag();
+
+    // Move on window and keep button pressed.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                        .buttonState(AMOTION_EVENT_BUTTON_STYLUS_PRIMARY)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS)
+                                                         .x(50)
+                                                         .y(50))
+                                        .build()))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    mWindow->consumeDragEvent(false, 50, 50);
+    mSecondWindow->assertNoEvents();
+
+    // Move to another window and release button, expect to drop item.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_MOVE, AINPUT_SOURCE_STYLUS)
+                                        .buttonState(0)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS)
+                                                         .x(150)
+                                                         .y(50))
+                                        .build()))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    mDragWindow->consumeMotionMove(ADISPLAY_ID_DEFAULT);
+    mWindow->assertNoEvents();
+    mSecondWindow->assertNoEvents();
+    mFakePolicy->assertDropTargetEquals(mSecondWindow->getToken());
+
+    // nothing to the window.
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED,
+              injectMotionEvent(mDispatcher,
+                                MotionEventBuilder(AMOTION_EVENT_ACTION_UP, AINPUT_SOURCE_STYLUS)
+                                        .buttonState(0)
+                                        .pointer(PointerBuilder(0, AMOTION_EVENT_TOOL_TYPE_STYLUS)
+                                                         .x(150)
+                                                         .y(50))
+                                        .build()))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    mDragWindow->consumeMotionUp(ADISPLAY_ID_DEFAULT);
+    mWindow->assertNoEvents();
+    mSecondWindow->assertNoEvents();
+}
+
 } // namespace android::inputdispatcher