summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Arpit Singh <arpitks@google.com> 2024-06-03 09:48:16 +0000
committer Arpit Singh <arpitks@google.com> 2024-06-12 16:50:31 +0000
commitb65e2bd8ed1f3a87fe600cd23bf84cecb4105b57 (patch)
tree94212f5d19fbf865b8591390436786dee129617a
parent33bc33c39589c8e8d25efbab8c968f80c7508082 (diff)
Dismiss the mouse pointer while typing on keyboard
Fixes the borken UX to dismiss the mouse pointer while user is typing on the physical keyboard. Bug: b/338652288 Test: atest inputflinger_tests Change-Id: Ifc4bfd20a44650634d007fbcfc75bf497d5f4623
-rw-r--r--services/inputflinger/PointerChoreographer.cpp34
-rw-r--r--services/inputflinger/PointerChoreographer.h8
-rw-r--r--services/inputflinger/dispatcher/InputDispatcher.cpp7
-rw-r--r--services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h5
-rw-r--r--services/inputflinger/include/NotifyArgsBuilders.h7
-rw-r--r--services/inputflinger/include/PointerChoreographerPolicyInterface.h3
-rw-r--r--services/inputflinger/reader/mapper/KeyboardInputMapper.cpp1
-rw-r--r--services/inputflinger/tests/FakeInputDispatcherPolicy.cpp24
-rw-r--r--services/inputflinger/tests/FakeInputDispatcherPolicy.h5
-rw-r--r--services/inputflinger/tests/InputDispatcher_test.cpp8
-rw-r--r--services/inputflinger/tests/InterfaceMocks.h1
-rw-r--r--services/inputflinger/tests/KeyboardInputMapper_test.cpp45
-rw-r--r--services/inputflinger/tests/PointerChoreographer_test.cpp179
13 files changed, 281 insertions, 46 deletions
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 7d3a2df550..00dd6ba62b 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -21,6 +21,7 @@
#if defined(__ANDROID__)
#include <gui/SurfaceComposerClient.h>
#endif
+#include <input/Keyboard.h>
#include <input/PrintTools.h>
#include <unordered_set>
@@ -137,6 +138,7 @@ PointerChoreographer::PointerChoreographer(
mNotifiedPointerDisplayId(ui::LogicalDisplayId::INVALID),
mShowTouchesEnabled(false),
mStylusPointerIconEnabled(false),
+ mCurrentFocusedDisplay(ui::LogicalDisplayId::DEFAULT),
mRegisterListener(registerListener),
mUnregisterListener(unregisterListener) {}
@@ -168,6 +170,7 @@ void PointerChoreographer::notifyConfigurationChanged(const NotifyConfigurationC
}
void PointerChoreographer::notifyKey(const NotifyKeyArgs& args) {
+ fadeMouseCursorOnKeyPress(args);
mNextListener.notify(args);
}
@@ -177,6 +180,32 @@ void PointerChoreographer::notifyMotion(const NotifyMotionArgs& args) {
mNextListener.notify(newArgs);
}
+void PointerChoreographer::fadeMouseCursorOnKeyPress(const android::NotifyKeyArgs& args) {
+ if (args.action == AKEY_EVENT_ACTION_UP || isMetaKey(args.keyCode)) {
+ return;
+ }
+ // Meta state for these keys is ignored for dismissing cursor while typing
+ constexpr static int32_t ALLOW_FADING_META_STATE_MASK = AMETA_CAPS_LOCK_ON | AMETA_NUM_LOCK_ON |
+ AMETA_SCROLL_LOCK_ON | AMETA_SHIFT_LEFT_ON | AMETA_SHIFT_RIGHT_ON | AMETA_SHIFT_ON;
+ if (args.metaState & ~ALLOW_FADING_META_STATE_MASK) {
+ // Do not fade if any other meta state is active
+ return;
+ }
+ if (!mPolicy.isInputMethodConnectionActive()) {
+ return;
+ }
+
+ std::scoped_lock _l(mLock);
+ ui::LogicalDisplayId targetDisplay = args.displayId;
+ if (targetDisplay == ui::LogicalDisplayId::INVALID) {
+ targetDisplay = mCurrentFocusedDisplay;
+ }
+ auto it = mMousePointersByDisplay.find(targetDisplay);
+ if (it != mMousePointersByDisplay.end()) {
+ it->second->fade(PointerControllerInterface::Transition::GRADUAL);
+ }
+}
+
NotifyMotionArgs PointerChoreographer::processMotion(const NotifyMotionArgs& args) {
std::scoped_lock _l(mLock);
@@ -806,6 +835,11 @@ void PointerChoreographer::setPointerIconVisibility(ui::LogicalDisplayId display
}
}
+void PointerChoreographer::setFocusedDisplay(ui::LogicalDisplayId displayId) {
+ std::scoped_lock lock(mLock);
+ mCurrentFocusedDisplay = displayId;
+}
+
PointerChoreographer::ControllerConstructor PointerChoreographer::getMouseControllerConstructor(
ui::LogicalDisplayId displayId) {
std::function<std::shared_ptr<PointerControllerInterface>()> ctor =
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index d9b075f3ee..aaf1e3e962 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -76,6 +76,11 @@ public:
virtual void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) = 0;
/**
+ * Used by Dispatcher to notify changes in the current focused display.
+ */
+ virtual void setFocusedDisplay(ui::LogicalDisplayId displayId) = 0;
+
+ /**
* This method may be called on any thread (usually by the input manager on a binder thread).
*/
virtual void dump(std::string& dump) = 0;
@@ -97,6 +102,7 @@ public:
bool setPointerIcon(std::variant<std::unique_ptr<SpriteIcon>, PointerIconStyle> icon,
ui::LogicalDisplayId displayId, DeviceId deviceId) override;
void setPointerIconVisibility(ui::LogicalDisplayId displayId, bool visible) override;
+ void setFocusedDisplay(ui::LogicalDisplayId displayId) override;
void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
@@ -124,6 +130,7 @@ private:
InputDeviceInfo* findInputDeviceLocked(DeviceId deviceId) REQUIRES(mLock);
bool canUnfadeOnDisplay(ui::LogicalDisplayId displayId) REQUIRES(mLock);
+ void fadeMouseCursorOnKeyPress(const NotifyKeyArgs& args);
NotifyMotionArgs processMotion(const NotifyMotionArgs& args);
NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
NotifyMotionArgs processTouchpadEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
@@ -192,6 +199,7 @@ private:
bool mShowTouchesEnabled GUARDED_BY(mLock);
bool mStylusPointerIconEnabled GUARDED_BY(mLock);
std::set<ui::LogicalDisplayId /*displayId*/> mDisplaysWithPointersHidden;
+ ui::LogicalDisplayId mCurrentFocusedDisplay GUARDED_BY(mLock);
protected:
using WindowListenerRegisterConsumer = std::function<std::vector<gui::WindowInfo>(
diff --git a/services/inputflinger/dispatcher/InputDispatcher.cpp b/services/inputflinger/dispatcher/InputDispatcher.cpp
index 5ed5eb8e33..47c288931d 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -5526,6 +5526,13 @@ void InputDispatcher::setFocusedDisplay(ui::LogicalDisplayId displayId) {
synthesizeCancelationEventsForWindowLocked(windowHandle, options);
}
mFocusedDisplayId = displayId;
+ // Enqueue a command to run outside the lock to tell the policy that the focused display
+ // changed.
+ auto command = [this]() REQUIRES(mLock) {
+ scoped_unlock unlock(mLock);
+ mPolicy.notifyFocusedDisplayChanged(mFocusedDisplayId);
+ };
+ postCommandLocked(std::move(command));
// Only a window on the focused display can have Pointer Capture, so disable the active
// Pointer Capture session if there is one, since the focused display changed.
diff --git a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
index 0f03620f05..65fb76d274 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -76,6 +76,11 @@ public:
InputDeviceSensorAccuracy accuracy) = 0;
virtual void notifyVibratorState(int32_t deviceId, bool isOn) = 0;
+ /*
+ * Notifies the system that focused display has changed.
+ */
+ virtual void notifyFocusedDisplayChanged(ui::LogicalDisplayId displayId) = 0;
+
/* Filters an input event.
* Return true to dispatch the event unmodified, false to consume the event.
* A filter can also transform and inject events later by passing POLICY_FLAG_FILTERED
diff --git a/services/inputflinger/include/NotifyArgsBuilders.h b/services/inputflinger/include/NotifyArgsBuilders.h
index cae638f7bf..5b94d57b8e 100644
--- a/services/inputflinger/include/NotifyArgsBuilders.h
+++ b/services/inputflinger/include/NotifyArgsBuilders.h
@@ -21,6 +21,7 @@
#include <attestation/HmacKeyManager.h>
#include <input/Input.h>
#include <input/InputEventBuilders.h>
+#include <input/Keyboard.h>
#include <utils/Timers.h> // for nsecs_t, systemTime
#include <vector>
@@ -206,6 +207,12 @@ public:
return *this;
}
+ KeyArgsBuilder& metaState(int32_t metaState) {
+ mMetaState |= metaState;
+ mMetaState = normalizeMetaState(/*oldMetaState=*/mMetaState);
+ return *this;
+ }
+
NotifyKeyArgs build() const {
return {mEventId,
mEventTime,
diff --git a/services/inputflinger/include/PointerChoreographerPolicyInterface.h b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
index f6dc10997a..7a85c12559 100644
--- a/services/inputflinger/include/PointerChoreographerPolicyInterface.h
+++ b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
@@ -55,6 +55,9 @@ public:
*/
virtual void notifyPointerDisplayIdChanged(ui::LogicalDisplayId displayId,
const FloatPoint& position) = 0;
+
+ /* Returns true if any InputConnection is currently active. */
+ virtual bool isInputMethodConnectionActive() = 0;
};
} // namespace android
diff --git a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
index 91ec62d3d4..25f4893baf 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -493,7 +493,6 @@ std::list<NotifyArgs> KeyboardInputMapper::cancelAllDownKeys(nsecs_t when) {
void KeyboardInputMapper::onKeyDownProcessed(nsecs_t downTime) {
InputReaderContext& context = *getContext();
context.setLastKeyDownTimestamp(downTime);
- // TODO(b/338652288): Move cursor fading logic into PointerChoreographer.
// Ignore meta keys or multiple simultaneous down keys as they are likely to be keyboard
// shortcuts
bool shouldHideCursor = mKeyDowns.size() == 1 && !isMetaKey(mKeyDowns[0].keyCode);
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
index e17ee3a5d9..3df05f4bae 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
@@ -219,6 +219,24 @@ void FakeInputDispatcherPolicy::setConsumeKeyBeforeDispatching(bool consumeKeyBe
mConsumeKeyBeforeDispatching = consumeKeyBeforeDispatching;
}
+void FakeInputDispatcherPolicy::assertFocusedDisplayNotified(ui::LogicalDisplayId expectedDisplay) {
+ std::unique_lock lock(mLock);
+ base::ScopedLockAssertion assumeLocked(mLock);
+
+ if (!mFocusedDisplayNotifiedCondition.wait_for(lock, 100ms,
+ [this, expectedDisplay]() REQUIRES(mLock) {
+ if (!mNotifiedFocusedDisplay.has_value() ||
+ mNotifiedFocusedDisplay.value() !=
+ expectedDisplay) {
+ return false;
+ }
+ return true;
+ })) {
+ ADD_FAILURE() << "Timed out waiting for notifyFocusedDisplayChanged(" << expectedDisplay
+ << ") to be called.";
+ }
+}
+
void FakeInputDispatcherPolicy::assertUserActivityNotPoked() {
std::unique_lock lock(mLock);
base::ScopedLockAssertion assumeLocked(mLock);
@@ -473,4 +491,10 @@ void FakeInputDispatcherPolicy::assertFilterInputEventWasCalledInternal(
mFilteredEvent = nullptr;
}
+void FakeInputDispatcherPolicy::notifyFocusedDisplayChanged(ui::LogicalDisplayId displayId) {
+ std::scoped_lock lock(mLock);
+ mNotifiedFocusedDisplay = displayId;
+ mFocusedDisplayNotifiedCondition.notify_all();
+}
+
} // namespace android
diff --git a/services/inputflinger/tests/FakeInputDispatcherPolicy.h b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
index 62ff10f8c8..a0f3ea9008 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
@@ -116,6 +116,7 @@ public:
void assertUnhandledKeyReported(int32_t keycode);
void assertUnhandledKeyNotReported();
void setConsumeKeyBeforeDispatching(bool consumeKeyBeforeDispatching);
+ void assertFocusedDisplayNotified(ui::LogicalDisplayId expectedDisplay);
private:
std::mutex mLock;
@@ -126,6 +127,9 @@ private:
std::condition_variable mPointerCaptureChangedCondition;
+ std::optional<ui::LogicalDisplayId> mNotifiedFocusedDisplay GUARDED_BY(mLock);
+ std::condition_variable mFocusedDisplayNotifiedCondition;
+
std::optional<PointerCaptureRequest> mPointerCaptureRequest GUARDED_BY(mLock);
// ANR handling
std::queue<std::shared_ptr<InputApplicationHandle>> mAnrApplications GUARDED_BY(mLock);
@@ -201,6 +205,7 @@ private:
void notifyDropWindow(const sp<IBinder>& token, float x, float y) override;
void notifyDeviceInteraction(int32_t deviceId, nsecs_t timestamp,
const std::set<gui::Uid>& uids) override;
+ void notifyFocusedDisplayChanged(ui::LogicalDisplayId displayId) override;
void assertFilterInputEventWasCalledInternal(
const std::function<void(const InputEvent&)>& verify);
diff --git a/services/inputflinger/tests/InputDispatcher_test.cpp b/services/inputflinger/tests/InputDispatcher_test.cpp
index 56a05a3a3c..aa1462a2ff 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -8577,6 +8577,8 @@ public:
// Set focus to second display window.
// Set focus display to second one.
mDispatcher->setFocusedDisplay(SECOND_DISPLAY_ID);
+ mFakePolicy->assertFocusedDisplayNotified(SECOND_DISPLAY_ID);
+
// Set focus window for second display.
mDispatcher->setFocusedApplication(SECOND_DISPLAY_ID, application2);
windowInSecondary->setFocusable(true);
@@ -11066,6 +11068,7 @@ TEST_F(InputDispatcherPointerCaptureTests, MultiDisplayPointerCapture) {
// Make the second display the focused display.
mDispatcher->setFocusedDisplay(SECOND_DISPLAY_ID);
+ mFakePolicy->assertFocusedDisplayNotified(SECOND_DISPLAY_ID);
// This causes the first window to lose pointer capture, and it's unable to request capture.
mWindow->consumeCaptureEvent(false);
@@ -13769,4 +13772,9 @@ TEST_F(InputDispatcherPointerInWindowTest, MultipleDevicesControllingOneMouse) {
/*pointerId=*/0));
}
+TEST_F(InputDispatcherTest, FocusedDisplayChangeIsNotified) {
+ mDispatcher->setFocusedDisplay(SECOND_DISPLAY_ID);
+ mFakePolicy->assertFocusedDisplayNotified(SECOND_DISPLAY_ID);
+}
+
} // namespace android::inputdispatcher
diff --git a/services/inputflinger/tests/InterfaceMocks.h b/services/inputflinger/tests/InterfaceMocks.h
index 44417246d2..16d3193908 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -186,6 +186,7 @@ public:
(PointerControllerInterface::ControllerType), (override));
MOCK_METHOD(void, notifyPointerDisplayIdChanged,
(ui::LogicalDisplayId displayId, const FloatPoint& position), (override));
+ MOCK_METHOD(bool, isInputMethodConnectionActive, (), (override));
};
} // namespace android
diff --git a/services/inputflinger/tests/KeyboardInputMapper_test.cpp b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
index ada841d28d..ab47cc67b1 100644
--- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp
+++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
@@ -70,15 +70,6 @@ protected:
AINPUT_SOURCE_KEYBOARD);
}
- void testPointerVisibilityForKeys(const std::vector<int32_t>& keyCodes, bool expectVisible) {
- for (int32_t keyCode : keyCodes) {
- process(EV_KEY, keyCode, 1);
- process(EV_SYN, SYN_REPORT, 0);
- process(EV_KEY, keyCode, 0);
- process(EV_SYN, SYN_REPORT, 0);
- }
- }
-
void testTouchpadTapStateForKeys(const std::vector<int32_t>& keyCodes,
const bool expectPrevent) {
if (expectPrevent) {
@@ -95,42 +86,6 @@ protected:
};
/**
- * Pointer visibility should remain unaffected if there is no active Input Method Connection
- */
-TEST_F(KeyboardInputMapperUnitTest, KeystrokesWithoutIMeConnectionDoesNotHidePointer) {
- testPointerVisibilityForKeys({KEY_0, KEY_A, KEY_LEFTCTRL}, /* expectVisible= */ true);
-}
-
-/**
- * Pointer should hide if there is a active Input Method Connection
- */
-TEST_F(KeyboardInputMapperUnitTest, AlphanumericKeystrokesWithIMeConnectionHidePointer) {
- mFakePolicy->setIsInputMethodConnectionActive(true);
- testPointerVisibilityForKeys({KEY_0, KEY_A}, /* expectVisible= */ false);
-}
-
-/**
- * Pointer should still hide if touchpad taps are already disabled
- */
-TEST_F(KeyboardInputMapperUnitTest, AlphanumericKeystrokesWithTouchpadTapDisabledHidePointer) {
- mFakePolicy->setIsInputMethodConnectionActive(true);
- EXPECT_CALL(mMockInputReaderContext, isPreventingTouchpadTaps).WillRepeatedly(Return(true));
- testPointerVisibilityForKeys({KEY_0, KEY_A}, /* expectVisible= */ false);
-}
-
-/**
- * Pointer visibility should remain unaffected by meta keys even if Input Method Connection is
- * active
- */
-TEST_F(KeyboardInputMapperUnitTest, MetaKeystrokesWithIMeConnectionDoesNotHidePointer) {
- mFakePolicy->setIsInputMethodConnectionActive(true);
- std::vector<int32_t> metaKeys{KEY_LEFTALT, KEY_RIGHTALT, KEY_LEFTSHIFT, KEY_RIGHTSHIFT,
- KEY_FN, KEY_LEFTCTRL, KEY_RIGHTCTRL, KEY_LEFTMETA,
- KEY_RIGHTMETA, KEY_CAPSLOCK, KEY_NUMLOCK, KEY_SCROLLLOCK};
- testPointerVisibilityForKeys(metaKeys, /* expectVisible= */ true);
-}
-
-/**
* Touchpad tap should not be disabled if there is no active Input Method Connection
*/
TEST_F(KeyboardInputMapperUnitTest, KeystrokesWithoutIMeConnectionDontDisableTouchpadTap) {
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 3f2d6ec45c..9a5b6a73f5 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -2294,6 +2294,185 @@ TEST_F(PointerChoreographerTest, MouseAndDrawingTabletReportMouseEvents) {
assertPointerControllerRemoved(pc);
}
+class PointerVisibilityOnKeyPressTest : public PointerChoreographerTest {
+protected:
+ const std::unordered_map<int32_t, int32_t>
+ mMetaKeyStates{{AKEYCODE_ALT_LEFT, AMETA_ALT_LEFT_ON},
+ {AKEYCODE_ALT_RIGHT, AMETA_ALT_RIGHT_ON},
+ {AKEYCODE_SHIFT_LEFT, AMETA_SHIFT_LEFT_ON},
+ {AKEYCODE_SHIFT_RIGHT, AMETA_SHIFT_RIGHT_ON},
+ {AKEYCODE_SYM, AMETA_SYM_ON},
+ {AKEYCODE_FUNCTION, AMETA_FUNCTION_ON},
+ {AKEYCODE_CTRL_LEFT, AMETA_CTRL_LEFT_ON},
+ {AKEYCODE_CTRL_RIGHT, AMETA_CTRL_RIGHT_ON},
+ {AKEYCODE_META_LEFT, AMETA_META_LEFT_ON},
+ {AKEYCODE_META_RIGHT, AMETA_META_RIGHT_ON},
+ {AKEYCODE_CAPS_LOCK, AMETA_CAPS_LOCK_ON},
+ {AKEYCODE_NUM_LOCK, AMETA_NUM_LOCK_ON},
+ {AKEYCODE_SCROLL_LOCK, AMETA_SCROLL_LOCK_ON}};
+
+ void notifyKey(ui::LogicalDisplayId targetDisplay, int32_t keyCode,
+ int32_t metaState = AMETA_NONE) {
+ if (metaState == AMETA_NONE && mMetaKeyStates.contains(keyCode)) {
+ // For simplicity, we always set the corresponding meta state when sending a meta
+ // keycode. This does not take into consideration when the meta state is updated in
+ // reality.
+ metaState = mMetaKeyStates.at(keyCode);
+ }
+ mChoreographer.notifyKey(KeyArgsBuilder(AKEY_EVENT_ACTION_DOWN, AINPUT_SOURCE_KEYBOARD)
+ .displayId(targetDisplay)
+ .keyCode(keyCode)
+ .metaState(metaState)
+ .build());
+ mChoreographer.notifyKey(KeyArgsBuilder(AKEY_EVENT_ACTION_UP, AINPUT_SOURCE_KEYBOARD)
+ .displayId(targetDisplay)
+ .keyCode(keyCode)
+ .metaState(metaState)
+ .build());
+ }
+
+ void metaKeyCombinationHidesPointer(FakePointerController& pc, int32_t keyCode,
+ int32_t metaKeyCode) {
+ ASSERT_TRUE(pc.isPointerShown());
+ notifyKey(DISPLAY_ID, keyCode, mMetaKeyStates.at(metaKeyCode));
+ ASSERT_FALSE(pc.isPointerShown());
+
+ unfadePointer();
+ }
+
+ void metaKeyCombinationDoesNotHidePointer(FakePointerController& pc, int32_t keyCode,
+ int32_t metaKeyCode) {
+ ASSERT_TRUE(pc.isPointerShown());
+ notifyKey(DISPLAY_ID, keyCode, mMetaKeyStates.at(metaKeyCode));
+ ASSERT_TRUE(pc.isPointerShown());
+ }
+
+ void unfadePointer() {
+ // unfade pointer by injecting mose hover event
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_ENTER, AINPUT_SOURCE_MOUSE)
+ .pointer(MOUSE_POINTER)
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ }
+};
+
+TEST_F(PointerVisibilityOnKeyPressTest, KeystrokesWithoutImeConnectionDoesNotHidePointer) {
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+ // Mouse connected
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_TRUE(pc->isPointerShown());
+
+ notifyKey(ui::LogicalDisplayId::INVALID, AKEYCODE_0);
+ notifyKey(ui::LogicalDisplayId::INVALID, AKEYCODE_A);
+ notifyKey(ui::LogicalDisplayId::INVALID, AKEYCODE_CTRL_LEFT);
+
+ ASSERT_TRUE(pc->isPointerShown());
+}
+
+TEST_F(PointerVisibilityOnKeyPressTest, AlphanumericKeystrokesWithImeConnectionHidePointer) {
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+ // Mouse connected
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_TRUE(pc->isPointerShown());
+
+ EXPECT_CALL(mMockPolicy, isInputMethodConnectionActive).WillRepeatedly(testing::Return(true));
+
+ notifyKey(DISPLAY_ID, AKEYCODE_0);
+ ASSERT_FALSE(pc->isPointerShown());
+
+ unfadePointer();
+
+ notifyKey(DISPLAY_ID, AKEYCODE_A);
+ ASSERT_FALSE(pc->isPointerShown());
+}
+
+TEST_F(PointerVisibilityOnKeyPressTest, MetaKeystrokesDoNotHidePointer) {
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+ // Mouse connected
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_TRUE(pc->isPointerShown());
+
+ EXPECT_CALL(mMockPolicy, isInputMethodConnectionActive).WillRepeatedly(testing::Return(true));
+
+ const std::vector<int32_t> metaKeyCodes{AKEYCODE_ALT_LEFT, AKEYCODE_ALT_RIGHT,
+ AKEYCODE_SHIFT_LEFT, AKEYCODE_SHIFT_RIGHT,
+ AKEYCODE_SYM, AKEYCODE_FUNCTION,
+ AKEYCODE_CTRL_LEFT, AKEYCODE_CTRL_RIGHT,
+ AKEYCODE_META_LEFT, AKEYCODE_META_RIGHT,
+ AKEYCODE_CAPS_LOCK, AKEYCODE_NUM_LOCK,
+ AKEYCODE_SCROLL_LOCK};
+ for (int32_t keyCode : metaKeyCodes) {
+ notifyKey(ui::LogicalDisplayId::INVALID, keyCode);
+ }
+
+ ASSERT_TRUE(pc->isPointerShown());
+}
+
+TEST_F(PointerVisibilityOnKeyPressTest, KeystrokesWithoutTargetHidePointerOnlyOnFocusedDisplay) {
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+ mChoreographer.setFocusedDisplay(DISPLAY_ID);
+
+ // Mouse connected
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID),
+ generateTestDeviceInfo(SECOND_DEVICE_ID, AINPUT_SOURCE_MOUSE, ANOTHER_DISPLAY_ID)}});
+ auto pc1 = assertPointerControllerCreated(ControllerType::MOUSE);
+ auto pc2 = assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_TRUE(pc1->isPointerShown());
+ ASSERT_TRUE(pc2->isPointerShown());
+
+ EXPECT_CALL(mMockPolicy, isInputMethodConnectionActive).WillRepeatedly(testing::Return(true));
+
+ notifyKey(ui::LogicalDisplayId::INVALID, AKEYCODE_0);
+ ASSERT_FALSE(pc1->isPointerShown());
+ ASSERT_TRUE(pc2->isPointerShown());
+ unfadePointer();
+
+ notifyKey(ui::LogicalDisplayId::INVALID, AKEYCODE_A);
+ ASSERT_FALSE(pc1->isPointerShown());
+ ASSERT_TRUE(pc2->isPointerShown());
+}
+
+TEST_F(PointerVisibilityOnKeyPressTest, TestMetaKeyCombinations) {
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+
+ // Mouse connected
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+ auto pc = assertPointerControllerCreated(ControllerType::MOUSE);
+ EXPECT_CALL(mMockPolicy, isInputMethodConnectionActive).WillRepeatedly(testing::Return(true));
+
+ // meta key combinations that should hide pointer
+ metaKeyCombinationHidesPointer(*pc, AKEYCODE_A, AKEYCODE_SHIFT_LEFT);
+ metaKeyCombinationHidesPointer(*pc, AKEYCODE_A, AKEYCODE_SHIFT_RIGHT);
+ metaKeyCombinationHidesPointer(*pc, AKEYCODE_A, AKEYCODE_CAPS_LOCK);
+ metaKeyCombinationHidesPointer(*pc, AKEYCODE_0, AKEYCODE_NUM_LOCK);
+ metaKeyCombinationHidesPointer(*pc, AKEYCODE_A, AKEYCODE_SCROLL_LOCK);
+
+ // meta key combinations that should not hide pointer
+ metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_ALT_LEFT);
+ metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_ALT_RIGHT);
+ metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_CTRL_LEFT);
+ metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_CTRL_RIGHT);
+ metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_SYM);
+ metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_FUNCTION);
+ metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_META_LEFT);
+ metaKeyCombinationDoesNotHidePointer(*pc, AKEYCODE_A, AKEYCODE_META_RIGHT);
+}
+
class PointerChoreographerWindowInfoListenerTest : public testing::Test {};
TEST_F_WITH_FLAGS(