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
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index 7d3a2df..00dd6ba 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 @@
mNotifiedPointerDisplayId(ui::LogicalDisplayId::INVALID),
mShowTouchesEnabled(false),
mStylusPointerIconEnabled(false),
+ mCurrentFocusedDisplay(ui::LogicalDisplayId::DEFAULT),
mRegisterListener(registerListener),
mUnregisterListener(unregisterListener) {}
@@ -168,6 +170,7 @@
}
void PointerChoreographer::notifyKey(const NotifyKeyArgs& args) {
+ fadeMouseCursorOnKeyPress(args);
mNextListener.notify(args);
}
@@ -177,6 +180,32 @@
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::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 d9b075f..aaf1e3e 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -76,6 +76,11 @@
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 @@
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 @@
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 @@
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 5ed5eb8..47c2889 100644
--- a/services/inputflinger/dispatcher/InputDispatcher.cpp
+++ b/services/inputflinger/dispatcher/InputDispatcher.cpp
@@ -5526,6 +5526,13 @@
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 0f03620..65fb76d 100644
--- a/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
+++ b/services/inputflinger/dispatcher/include/InputDispatcherPolicyInterface.h
@@ -76,6 +76,11 @@
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 cae638f..5b94d57 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 @@
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 f6dc109..7a85c12 100644
--- a/services/inputflinger/include/PointerChoreographerPolicyInterface.h
+++ b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
@@ -55,6 +55,9 @@
*/
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 91ec62d..25f4893 100644
--- a/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/KeyboardInputMapper.cpp
@@ -493,7 +493,6 @@
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 e17ee3a..3df05f4 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.cpp
@@ -219,6 +219,24 @@
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 @@
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 62ff10f..a0f3ea9 100644
--- a/services/inputflinger/tests/FakeInputDispatcherPolicy.h
+++ b/services/inputflinger/tests/FakeInputDispatcherPolicy.h
@@ -116,6 +116,7 @@
void assertUnhandledKeyReported(int32_t keycode);
void assertUnhandledKeyNotReported();
void setConsumeKeyBeforeDispatching(bool consumeKeyBeforeDispatching);
+ void assertFocusedDisplayNotified(ui::LogicalDisplayId expectedDisplay);
private:
std::mutex mLock;
@@ -126,6 +127,9 @@
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 @@
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 56a05a3..aa1462a 100644
--- a/services/inputflinger/tests/InputDispatcher_test.cpp
+++ b/services/inputflinger/tests/InputDispatcher_test.cpp
@@ -8577,6 +8577,8 @@
// 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 @@
// 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 @@
/*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 4441724..16d3193 100644
--- a/services/inputflinger/tests/InterfaceMocks.h
+++ b/services/inputflinger/tests/InterfaceMocks.h
@@ -186,6 +186,7 @@
(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 ada841d..ab47cc6 100644
--- a/services/inputflinger/tests/KeyboardInputMapper_test.cpp
+++ b/services/inputflinger/tests/KeyboardInputMapper_test.cpp
@@ -70,15 +70,6 @@
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 @@
};
/**
- * 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 3f2d6ec..9a5b6a7 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -2294,6 +2294,185 @@
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(