Merge "Add NO_FOCUS_CHANGE flag to pointer gestures to disallow focus changes" into sc-dev
diff --git a/include/input/Input.h b/include/input/Input.h
index a23ed98..d4defa8 100644
--- a/include/input/Input.h
+++ b/include/input/Input.h
@@ -70,6 +70,14 @@
      */
     AMOTION_EVENT_FLAG_IS_GENERATED_GESTURE = 0x8,
 
+    /**
+     * This flag indicates that the event will not cause a focus change if it is directed to an
+     * unfocused window, even if it an ACTION_DOWN. This is typically used with pointer
+     * gestures to allow the user to direct gestures to an unfocused window without bringing it
+     * into focus.
+     */
+    AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE = 0x40,
+
     /* Motion event is inconsistent with previously sent motion events. */
     AMOTION_EVENT_FLAG_TAINTED = 0x80000000,
 };
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 9a43ed9..446c913 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -2886,6 +2886,12 @@
                 ATRACE_NAME(message.c_str());
             }
 
+            if ((motionEntry.flags & AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE) &&
+                (motionEntry.policyFlags & POLICY_FLAG_TRUSTED)) {
+                // Skip reporting pointer down outside focus to the policy.
+                break;
+            }
+
             dispatchPointerDownOutsideFocus(motionEntry.source, dispatchEntry->resolvedAction,
                                             inputTarget.inputChannel->getConnectionToken());
 
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.cpp b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
index 13712ee..fb65484 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.cpp
@@ -2456,6 +2456,12 @@
     int32_t metaState = getContext()->getGlobalMetaState();
     int32_t buttonState = mCurrentCookedState.buttonState;
 
+    uint32_t flags = 0;
+
+    if (!PointerGesture::canGestureAffectWindowFocus(mPointerGesture.currentGestureMode)) {
+        flags |= AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE;
+    }
+
     // Update last coordinates of pointers that have moved so that we observe the new
     // pointer positions at the same time as other pointers that have just gone up.
     bool down = mPointerGesture.currentGestureMode == PointerGesture::Mode::TAP ||
@@ -2485,8 +2491,8 @@
     BitSet32 dispatchedGestureIdBits(mPointerGesture.lastGestureIdBits);
     if (!dispatchedGestureIdBits.isEmpty()) {
         if (cancelPreviousGesture) {
-            dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0, 0,
-                           metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+            dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_CANCEL, 0,
+                           flags, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
                            mPointerGesture.lastGestureProperties, mPointerGesture.lastGestureCoords,
                            mPointerGesture.lastGestureIdToIndex, dispatchedGestureIdBits, -1, 0, 0,
                            mPointerGesture.downTime);
@@ -2504,7 +2510,7 @@
                 uint32_t id = upGestureIdBits.clearFirstMarkedBit();
 
                 dispatchMotion(when, readTime, policyFlags, mSource,
-                               AMOTION_EVENT_ACTION_POINTER_UP, 0, 0, metaState, buttonState,
+                               AMOTION_EVENT_ACTION_POINTER_UP, 0, flags, metaState, buttonState,
                                AMOTION_EVENT_EDGE_FLAG_NONE, mPointerGesture.lastGestureProperties,
                                mPointerGesture.lastGestureCoords,
                                mPointerGesture.lastGestureIdToIndex, dispatchedGestureIdBits, id, 0,
@@ -2517,7 +2523,7 @@
 
     // Send motion events for all pointers that moved.
     if (moveNeeded) {
-        dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, 0,
+        dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, flags,
                        metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
                        mPointerGesture.currentGestureProperties,
                        mPointerGesture.currentGestureCoords,
@@ -2538,7 +2544,7 @@
             }
 
             dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_DOWN,
-                           0, 0, metaState, buttonState, 0,
+                           0, flags, metaState, buttonState, 0,
                            mPointerGesture.currentGestureProperties,
                            mPointerGesture.currentGestureCoords,
                            mPointerGesture.currentGestureIdToIndex, dispatchedGestureIdBits, id, 0,
@@ -2548,8 +2554,8 @@
 
     // Send motion events for hover.
     if (mPointerGesture.currentGestureMode == PointerGesture::Mode::HOVER) {
-        dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0,
-                       metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
+        dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_HOVER_MOVE, 0,
+                       flags, metaState, buttonState, AMOTION_EVENT_EDGE_FLAG_NONE,
                        mPointerGesture.currentGestureProperties,
                        mPointerGesture.currentGestureCoords,
                        mPointerGesture.currentGestureIdToIndex,
@@ -2573,7 +2579,7 @@
 
         const int32_t displayId = mPointerController->getDisplayId();
         NotifyMotionArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
-                              displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, 0,
+                              displayId, policyFlags, AMOTION_EVENT_ACTION_HOVER_MOVE, 0, flags,
                               metaState, buttonState, MotionClassification::NONE,
                               AMOTION_EVENT_EDGE_FLAG_NONE, 1, &pointerProperties, &pointerCoords,
                               0, 0, x, y, mPointerGesture.downTime, /* videoFrames */ {});
diff --git a/services/inputflinger/reader/mapper/TouchInputMapper.h b/services/inputflinger/reader/mapper/TouchInputMapper.h
index 5146299..920f842 100644
--- a/services/inputflinger/reader/mapper/TouchInputMapper.h
+++ b/services/inputflinger/reader/mapper/TouchInputMapper.h
@@ -590,6 +590,27 @@
             QUIET,
         };
 
+        // When a gesture is sent to an unfocused window, return true if it can bring that window
+        // into focus, false otherwise.
+        static bool canGestureAffectWindowFocus(Mode mode) {
+            switch (mode) {
+                case Mode::TAP:
+                case Mode::TAP_DRAG:
+                case Mode::BUTTON_CLICK_OR_DRAG:
+                    // Taps can affect window focus.
+                    return true;
+                case Mode::FREEFORM:
+                case Mode::HOVER:
+                case Mode::NEUTRAL:
+                case Mode::PRESS:
+                case Mode::QUIET:
+                case Mode::SWIPE:
+                    // Most gestures can be performed on an unfocused window, so they should not
+                    // not affect window focus.
+                    return false;
+            }
+        }
+
         // Time the first finger went down.
         nsecs_t firstTouchTime;
 
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 9687c83..bbf51f6 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -1225,6 +1225,11 @@
         return *this;
     }
 
+    MotionEventBuilder& addFlag(uint32_t flags) {
+        mFlags |= flags;
+        return *this;
+    }
+
     MotionEvent build() {
         std::vector<PointerProperties> pointerProperties;
         std::vector<PointerCoords> pointerCoords;
@@ -1244,7 +1249,7 @@
         MotionEvent event;
         ui::Transform identityTransform;
         event.initialize(InputEvent::nextId(), DEVICE_ID, mSource, mDisplayId, INVALID_HMAC,
-                         mAction, mActionButton, /* flags */ 0, /* edgeFlags */ 0, AMETA_NONE,
+                         mAction, mActionButton, mFlags, /* edgeFlags */ 0, AMETA_NONE,
                          mButtonState, MotionClassification::NONE, identityTransform,
                          /* xPrecision */ 0, /* yPrecision */ 0, mRawXCursorPosition,
                          mRawYCursorPosition, mDisplayWidth, mDisplayHeight, mEventTime, mEventTime,
@@ -1260,6 +1265,7 @@
     int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
     int32_t mActionButton{0};
     int32_t mButtonState{0};
+    int32_t mFlags{0};
     float mRawXCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
     float mRawYCursorPosition{AMOTION_EVENT_INVALID_CURSOR_POSITION};
     int32_t mDisplayWidth{AMOTION_EVENT_INVALID_DISPLAY_SIZE};
@@ -3105,6 +3111,25 @@
     mFakePolicy->assertOnPointerDownWasNotCalled();
 }
 
+// Have two windows, one with focus. Injecting a trusted DOWN MotionEvent with the flag
+// NO_FOCUS_CHANGE on the unfocused window should not call the onPointerDownOutsideFocus callback.
+TEST_F(InputDispatcherOnPointerDownOutsideFocus, NoFocusChangeFlag) {
+    const MotionEvent event =
+            MotionEventBuilder(AMOTION_EVENT_ACTION_DOWN, AINPUT_SOURCE_MOUSE)
+                    .eventTime(systemTime(SYSTEM_TIME_MONOTONIC))
+                    .pointer(PointerBuilder(/* id */ 0, AMOTION_EVENT_TOOL_TYPE_FINGER).x(20).y(20))
+                    .addFlag(AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE)
+                    .build();
+    ASSERT_EQ(InputEventInjectionResult::SUCCEEDED, injectMotionEvent(mDispatcher, event))
+            << "Inject motion event should return InputEventInjectionResult::SUCCEEDED";
+    mUnfocusedWindow->consumeAnyMotionDown(ADISPLAY_ID_DEFAULT, AMOTION_EVENT_FLAG_NO_FOCUS_CHANGE);
+
+    ASSERT_TRUE(mDispatcher->waitForIdle());
+    mFakePolicy->assertOnPointerDownWasNotCalled();
+    // Ensure that the unfocused window did not receive any FOCUS events.
+    mUnfocusedWindow->assertNoEvents();
+}
+
 // These tests ensures we can send touch events to a single client when there are multiple input
 // windows that point to the same client token.
 class InputDispatcherMultiWindowSameTokenTests : public InputDispatcherTest {