Pointer icon refactor for mouse
When PointerChoreographer is enabled, CursorInputMapper no longer
depends on the legacy PointerController. PointerChoreographer is
responsible for accumulating movements, fading/unfading pointers,
and deciding display/coordinates.
Test: atest inputflinger_tests
Bug: 293587049
Change-Id: I3a4fa4ab260673076d95dbcb0832d13d0fc34c75
diff --git a/services/inputflinger/PointerChoreographer.cpp b/services/inputflinger/PointerChoreographer.cpp
index e411abb..4dfd05f 100644
--- a/services/inputflinger/PointerChoreographer.cpp
+++ b/services/inputflinger/PointerChoreographer.cpp
@@ -16,17 +16,37 @@
#define LOG_TAG "PointerChoreographer"
+#include <android-base/logging.h>
+#include <input/PrintTools.h>
+
#include "PointerChoreographer.h"
+#define INDENT " "
+
namespace android {
+namespace {
+bool isFromMouse(const NotifyMotionArgs& args) {
+ return isFromSource(args.source, AINPUT_SOURCE_MOUSE) &&
+ args.pointerProperties[0].toolType == ToolType::MOUSE;
+}
+
+} // namespace
+
// --- PointerChoreographer ---
PointerChoreographer::PointerChoreographer(InputListenerInterface& listener,
PointerChoreographerPolicyInterface& policy)
- : mNextListener(listener) {}
+ : mNextListener(listener),
+ mPolicy(policy),
+ mDefaultMouseDisplayId(ADISPLAY_ID_DEFAULT),
+ mNotifiedPointerDisplayId(ADISPLAY_ID_NONE) {}
void PointerChoreographer::notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) {
+ std::scoped_lock _l(mLock);
+
+ mInputDeviceInfos = args.inputDeviceInfos;
+ updatePointerControllersLocked();
mNextListener.notify(args);
}
@@ -39,7 +59,70 @@
}
void PointerChoreographer::notifyMotion(const NotifyMotionArgs& args) {
- mNextListener.notify(args);
+ NotifyMotionArgs newArgs = processMotion(args);
+
+ mNextListener.notify(newArgs);
+}
+
+NotifyMotionArgs PointerChoreographer::processMotion(const NotifyMotionArgs& args) {
+ std::scoped_lock _l(mLock);
+
+ if (isFromMouse(args)) {
+ return processMouseEventLocked(args);
+ } else if (isFromSource(args.source, AINPUT_SOURCE_TOUCHSCREEN)) {
+ return processTouchscreenEventLocked(args);
+ }
+ return args;
+}
+
+NotifyMotionArgs PointerChoreographer::processMouseEventLocked(const NotifyMotionArgs& args) {
+ if (args.getPointerCount() != 1) {
+ LOG(FATAL) << "Wrong number of pointers " << args.dump();
+ }
+
+ const int32_t displayId = getTargetMouseDisplayLocked(args.displayId);
+ auto it = mMousePointersByDisplay.find(displayId);
+ if (it == mMousePointersByDisplay.end()) {
+ it = mMousePointersByDisplay
+ .insert({displayId,
+ mPolicy.createPointerController(
+ PointerControllerInterface::ControllerType::MOUSE)})
+ .first;
+ if (const auto viewport = findViewportByIdLocked(displayId); viewport) {
+ it->second->setDisplayViewport(*viewport);
+ }
+ notifyPointerDisplayIdChangedLocked();
+ }
+
+ PointerControllerInterface& pc = *it->second;
+
+ const float deltaX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+ const float deltaY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+ pc.move(deltaX, deltaY);
+ pc.unfade(PointerControllerInterface::Transition::IMMEDIATE);
+
+ const auto [x, y] = pc.getPosition();
+ NotifyMotionArgs newArgs(args);
+ newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_X, x);
+ newArgs.pointerCoords[0].setAxisValue(AMOTION_EVENT_AXIS_Y, y);
+ newArgs.xCursorPosition = x;
+ newArgs.yCursorPosition = y;
+ newArgs.displayId = displayId;
+ return newArgs;
+}
+
+/**
+ * When screen is touched, fade the mouse pointer on that display. We only call fade for
+ * ACTION_DOWN events.This would allow both mouse and touch to be used at the same time if the
+ * mouse device keeps moving and unfades the cursor.
+ * For touch events, we do not need to populate the cursor position.
+ */
+NotifyMotionArgs PointerChoreographer::processTouchscreenEventLocked(const NotifyMotionArgs& args) {
+ if (const auto it = mMousePointersByDisplay.find(args.displayId);
+ it != mMousePointersByDisplay.end() && args.action == AMOTION_EVENT_ACTION_DOWN) {
+ it->second->fade(PointerControllerInterface::Transition::GRADUAL);
+ }
+ return args;
}
void PointerChoreographer::notifySwitch(const NotifySwitchArgs& args) {
@@ -60,11 +143,123 @@
void PointerChoreographer::notifyPointerCaptureChanged(
const NotifyPointerCaptureChangedArgs& args) {
+ if (args.request.enable) {
+ std::scoped_lock _l(mLock);
+ for (const auto& [_, mousePointerController] : mMousePointersByDisplay) {
+ mousePointerController->fade(PointerControllerInterface::Transition::IMMEDIATE);
+ }
+ }
mNextListener.notify(args);
}
void PointerChoreographer::dump(std::string& dump) {
+ std::scoped_lock _l(mLock);
+
dump += "PointerChoreographer:\n";
+
+ dump += INDENT "MousePointerControllers:\n";
+ for (const auto& [displayId, mousePointerController] : mMousePointersByDisplay) {
+ std::string pointerControllerDump = addLinePrefix(mousePointerController->dump(), INDENT);
+ dump += INDENT + std::to_string(displayId) + " : " + pointerControllerDump;
+ }
+ dump += "\n";
+}
+
+const DisplayViewport* PointerChoreographer::findViewportByIdLocked(int32_t displayId) const {
+ for (auto& viewport : mViewports) {
+ if (viewport.displayId == displayId) {
+ return &viewport;
+ }
+ }
+ return nullptr;
+}
+
+int32_t PointerChoreographer::getTargetMouseDisplayLocked(int32_t associatedDisplayId) const {
+ return associatedDisplayId == ADISPLAY_ID_NONE ? mDefaultMouseDisplayId : associatedDisplayId;
+}
+
+void PointerChoreographer::updatePointerControllersLocked() {
+ std::set<int32_t /*displayId*/> mouseDisplaysToKeep;
+
+ // Mark the displayIds or deviceIds of PointerControllers currently needed.
+ for (const auto& info : mInputDeviceInfos) {
+ const uint32_t sources = info.getSources();
+ if (isFromSource(sources, AINPUT_SOURCE_MOUSE) ||
+ isFromSource(sources, AINPUT_SOURCE_MOUSE_RELATIVE)) {
+ const int32_t resolvedDisplayId =
+ getTargetMouseDisplayLocked(info.getAssociatedDisplayId());
+ mouseDisplaysToKeep.insert(resolvedDisplayId);
+ }
+ }
+
+ // Remove PointerControllers no longer needed.
+ // This has the side-effect of fading pointers or clearing spots before removal.
+ std::erase_if(mMousePointersByDisplay, [&mouseDisplaysToKeep](const auto& item) {
+ if (mouseDisplaysToKeep.find(item.first) == mouseDisplaysToKeep.end()) {
+ item.second->fade(PointerControllerInterface::Transition::IMMEDIATE);
+ return true;
+ }
+ return false;
+ });
+
+ // Notify the policy if there's a change on the pointer display ID.
+ notifyPointerDisplayIdChangedLocked();
+}
+
+void PointerChoreographer::notifyPointerDisplayIdChangedLocked() {
+ int32_t displayIdToNotify = ADISPLAY_ID_NONE;
+ FloatPoint cursorPosition = {0, 0};
+ if (const auto it = mMousePointersByDisplay.find(mDefaultMouseDisplayId);
+ it != mMousePointersByDisplay.end()) {
+ displayIdToNotify = it->second->getDisplayId();
+ cursorPosition = it->second->getPosition();
+ }
+
+ if (mNotifiedPointerDisplayId == displayIdToNotify) {
+ return;
+ }
+ mPolicy.notifyPointerDisplayIdChanged(displayIdToNotify, cursorPosition);
+ mNotifiedPointerDisplayId = displayIdToNotify;
+}
+
+void PointerChoreographer::setDefaultMouseDisplayId(int32_t displayId) {
+ std::scoped_lock _l(mLock);
+
+ mDefaultMouseDisplayId = displayId;
+ updatePointerControllersLocked();
+}
+
+void PointerChoreographer::setDisplayViewports(const std::vector<DisplayViewport>& viewports) {
+ std::scoped_lock _l(mLock);
+ for (const auto& viewport : viewports) {
+ int32_t displayId = viewport.displayId;
+ if (const auto it = mMousePointersByDisplay.find(displayId);
+ it != mMousePointersByDisplay.end()) {
+ it->second->setDisplayViewport(viewport);
+ }
+ }
+ mViewports = viewports;
+ notifyPointerDisplayIdChangedLocked();
+}
+
+std::optional<DisplayViewport> PointerChoreographer::getViewportForPointerDevice(
+ int32_t associatedDisplayId) {
+ std::scoped_lock _l(mLock);
+ const int32_t resolvedDisplayId = getTargetMouseDisplayLocked(associatedDisplayId);
+ if (const auto viewport = findViewportByIdLocked(resolvedDisplayId); viewport) {
+ return *viewport;
+ }
+ return std::nullopt;
+}
+
+FloatPoint PointerChoreographer::getMouseCursorPosition(int32_t displayId) {
+ std::scoped_lock _l(mLock);
+ const int32_t resolvedDisplayId = getTargetMouseDisplayLocked(displayId);
+ if (auto it = mMousePointersByDisplay.find(resolvedDisplayId);
+ it != mMousePointersByDisplay.end()) {
+ return it->second->getPosition();
+ }
+ return {AMOTION_EVENT_INVALID_CURSOR_POSITION, AMOTION_EVENT_INVALID_CURSOR_POSITION};
}
} // namespace android
diff --git a/services/inputflinger/PointerChoreographer.h b/services/inputflinger/PointerChoreographer.h
index 5e5f782..4300d70 100644
--- a/services/inputflinger/PointerChoreographer.h
+++ b/services/inputflinger/PointerChoreographer.h
@@ -20,6 +20,8 @@
#include "NotifyArgs.h"
#include "PointerChoreographerPolicyInterface.h"
+#include <android-base/thread_annotations.h>
+
namespace android {
/**
@@ -31,6 +33,15 @@
class PointerChoreographerInterface : public InputListenerInterface {
public:
/**
+ * Set the display that pointers, like the mouse cursor and drawing tablets,
+ * should be drawn on.
+ */
+ virtual void setDefaultMouseDisplayId(int32_t displayId) = 0;
+ virtual void setDisplayViewports(const std::vector<DisplayViewport>& viewports) = 0;
+ virtual std::optional<DisplayViewport> getViewportForPointerDevice(
+ int32_t associatedDisplayId = ADISPLAY_ID_NONE) = 0;
+ virtual FloatPoint getMouseCursorPosition(int32_t 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;
@@ -42,6 +53,12 @@
PointerChoreographerPolicyInterface&);
~PointerChoreographer() override = default;
+ void setDefaultMouseDisplayId(int32_t displayId) override;
+ void setDisplayViewports(const std::vector<DisplayViewport>& viewports) override;
+ std::optional<DisplayViewport> getViewportForPointerDevice(
+ int32_t associatedDisplayId) override;
+ FloatPoint getMouseCursorPosition(int32_t displayId) override;
+
void notifyInputDevicesChanged(const NotifyInputDevicesChangedArgs& args) override;
void notifyConfigurationChanged(const NotifyConfigurationChangedArgs& args) override;
void notifyKey(const NotifyKeyArgs& args) override;
@@ -55,7 +72,27 @@
void dump(std::string& dump) override;
private:
+ void updatePointerControllersLocked() REQUIRES(mLock);
+ void notifyPointerDisplayIdChangedLocked() REQUIRES(mLock);
+ const DisplayViewport* findViewportByIdLocked(int32_t displayId) const REQUIRES(mLock);
+ int32_t getTargetMouseDisplayLocked(int32_t associatedDisplayId) const REQUIRES(mLock);
+
+ NotifyMotionArgs processMotion(const NotifyMotionArgs& args);
+ NotifyMotionArgs processMouseEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+ NotifyMotionArgs processTouchscreenEventLocked(const NotifyMotionArgs& args) REQUIRES(mLock);
+
+ std::mutex mLock;
+
InputListenerInterface& mNextListener;
+ PointerChoreographerPolicyInterface& mPolicy;
+
+ std::map<int32_t, std::shared_ptr<PointerControllerInterface>> mMousePointersByDisplay
+ GUARDED_BY(mLock);
+
+ int32_t mDefaultMouseDisplayId GUARDED_BY(mLock);
+ int32_t mNotifiedPointerDisplayId GUARDED_BY(mLock);
+ std::vector<InputDeviceInfo> mInputDeviceInfos GUARDED_BY(mLock);
+ std::vector<DisplayViewport> mViewports GUARDED_BY(mLock);
};
} // namespace android
diff --git a/services/inputflinger/include/InputReaderBase.h b/services/inputflinger/include/InputReaderBase.h
index 25e1d21..e9737dd 100644
--- a/services/inputflinger/include/InputReaderBase.h
+++ b/services/inputflinger/include/InputReaderBase.h
@@ -451,6 +451,12 @@
/* Returns true if any InputConnection is currently active. */
virtual bool isInputMethodConnectionActive() = 0;
+
+ /* Gets the viewport of a particular display. The logical bounds of the viewport should be used
+ * as the range of possible values for pointing devices, like mice and touchpads.
+ */
+ virtual std::optional<DisplayViewport> getViewportForPointerDevice(
+ int32_t associatedDisplayId) = 0;
};
} // namespace android
diff --git a/services/inputflinger/include/PointerChoreographerPolicyInterface.h b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
index 9e020c7..93c0bdf 100644
--- a/services/inputflinger/include/PointerChoreographerPolicyInterface.h
+++ b/services/inputflinger/include/PointerChoreographerPolicyInterface.h
@@ -38,7 +38,10 @@
* library, libinputservice, that has the additional dependencies. The PointerController
* will be mocked when testing PointerChoreographer.
*/
- virtual std::shared_ptr<PointerControllerInterface> createPointerController() = 0;
+ virtual std::shared_ptr<PointerControllerInterface> createPointerController(
+ PointerControllerInterface::ControllerType type) = 0;
+
+ virtual void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) = 0;
};
} // namespace android
diff --git a/services/inputflinger/include/PointerControllerInterface.h b/services/inputflinger/include/PointerControllerInterface.h
index 95f819a..8837b25 100644
--- a/services/inputflinger/include/PointerControllerInterface.h
+++ b/services/inputflinger/include/PointerControllerInterface.h
@@ -52,6 +52,22 @@
virtual ~PointerControllerInterface() { }
public:
+ /**
+ * Enum used to differentiate various types of PointerControllers for the transition to
+ * using PointerChoreographer.
+ *
+ * TODO(b/293587049): Refactor the PointerController class into different controller types.
+ */
+ enum class ControllerType {
+ // The PointerController that is responsible for drawing all icons.
+ LEGACY,
+ // Represents a single mouse pointer.
+ MOUSE,
+ };
+
+ /* Dumps the state of the pointer controller. */
+ virtual std::string dump() = 0;
+
/* Gets the bounds of the region that the pointer can traverse.
* Returns true if the bounds are available. */
virtual std::optional<FloatRect> getBounds() const = 0;
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.cpp b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
index 79f07a5..35cdba0 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.cpp
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.cpp
@@ -20,6 +20,7 @@
#include "CursorInputMapper.h"
+#include <com_android_input_flags.h>
#include <optional>
#include "CursorButtonAccumulator.h"
@@ -29,11 +30,15 @@
#include "input/PrintTools.h"
+namespace input_flags = com::android::input::flags;
+
namespace android {
// The default velocity control parameters that has no effect.
static const VelocityControlParameters FLAT_VELOCITY_CONTROL_PARAMS{};
+static const DisplayViewport INVALID_VIEWPORT{};
+
// --- CursorMotionAccumulator ---
CursorMotionAccumulator::CursorMotionAccumulator() {
@@ -87,11 +92,11 @@
InputMapper::populateDeviceInfo(info);
if (mParameters.mode == Parameters::Mode::POINTER) {
- if (const auto bounds = mPointerController->getBounds(); bounds) {
- info.addMotionRange(AMOTION_EVENT_AXIS_X, mSource, bounds->left, bounds->right, 0.0f,
- 0.0f, 0.0f);
- info.addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, bounds->top, bounds->bottom, 0.0f,
- 0.0f, 0.0f);
+ if (!mBoundsInLogicalDisplay.isEmpty()) {
+ info.addMotionRange(AMOTION_EVENT_AXIS_X, mSource, mBoundsInLogicalDisplay.left,
+ mBoundsInLogicalDisplay.right, 0.0f, 0.0f, 0.0f);
+ info.addMotionRange(AMOTION_EVENT_AXIS_Y, mSource, mBoundsInLogicalDisplay.top,
+ mBoundsInLogicalDisplay.bottom, 0.0f, 0.0f, 0.0f);
}
} else {
info.addMotionRange(AMOTION_EVENT_AXIS_X, mSource, -1.0f, 1.0f, 0.0f, mXScale, 0.0f);
@@ -283,19 +288,22 @@
float xCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
float yCursorPosition = AMOTION_EVENT_INVALID_CURSOR_POSITION;
if (mSource == AINPUT_SOURCE_MOUSE) {
- if (moved || scrolled || buttonsChanged) {
- mPointerController->setPresentation(PointerControllerInterface::Presentation::POINTER);
+ if (!input_flags::enable_pointer_choreographer()) {
+ if (moved || scrolled || buttonsChanged) {
+ mPointerController->setPresentation(
+ PointerControllerInterface::Presentation::POINTER);
- if (moved) {
- mPointerController->move(deltaX, deltaY);
+ if (moved) {
+ mPointerController->move(deltaX, deltaY);
+ }
+ mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
}
- mPointerController->unfade(PointerControllerInterface::Transition::IMMEDIATE);
+
+ std::tie(xCursorPosition, yCursorPosition) = mPointerController->getPosition();
+
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
+ pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
}
-
- std::tie(xCursorPosition, yCursorPosition) = mPointerController->getPosition();
-
- pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_X, xCursorPosition);
- pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_Y, yCursorPosition);
pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X, deltaX);
pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y, deltaY);
} else {
@@ -499,31 +507,53 @@
const bool isPointer = mParameters.mode == Parameters::Mode::POINTER;
mDisplayId = ADISPLAY_ID_NONE;
- if (auto viewport = mDeviceContext.getAssociatedViewport(); viewport) {
+ DisplayViewport resolvedViewport = INVALID_VIEWPORT;
+ bool isBoundsSet = false;
+ if (auto assocViewport = mDeviceContext.getAssociatedViewport(); assocViewport) {
// This InputDevice is associated with a viewport.
// Only generate events for the associated display.
- const bool mismatchedPointerDisplay =
- isPointer && (viewport->displayId != mPointerController->getDisplayId());
- mDisplayId =
- mismatchedPointerDisplay ? std::nullopt : std::make_optional(viewport->displayId);
+ mDisplayId = assocViewport->displayId;
+ resolvedViewport = *assocViewport;
+ if (!input_flags::enable_pointer_choreographer()) {
+ const bool mismatchedPointerDisplay =
+ isPointer && (assocViewport->displayId != mPointerController->getDisplayId());
+ if (mismatchedPointerDisplay) {
+ // This device's associated display doesn't match PointerController's current
+ // display. Do not associate it with any display.
+ mDisplayId.reset();
+ }
+ }
} else if (isPointer) {
// The InputDevice is not associated with a viewport, but it controls the mouse pointer.
- mDisplayId = mPointerController->getDisplayId();
+ if (input_flags::enable_pointer_choreographer()) {
+ // Always use DISPLAY_ID_NONE for mouse events.
+ // PointerChoreographer will make it target the correct the displayId later.
+ const auto pointerViewport =
+ getContext()->getPolicy()->getViewportForPointerDevice(ADISPLAY_ID_NONE);
+ mDisplayId = pointerViewport ? std::make_optional(ADISPLAY_ID_NONE) : std::nullopt;
+ resolvedViewport = pointerViewport.value_or(INVALID_VIEWPORT);
+ } else {
+ mDisplayId = mPointerController->getDisplayId();
+ if (auto v = config.getDisplayViewportById(*mDisplayId); v) {
+ resolvedViewport = *v;
+ }
+ if (auto bounds = mPointerController->getBounds(); bounds) {
+ mBoundsInLogicalDisplay = *bounds;
+ isBoundsSet = true;
+ }
+ }
}
- mOrientation = ui::ROTATION_0;
- const bool isOrientedDevice =
- (mParameters.orientationAware && mParameters.hasAssociatedDisplay);
- // InputReader works in the un-rotated display coordinate space, so we don't need to do
- // anything if the device is already orientation-aware. If the device is not
- // orientation-aware, then we need to apply the inverse rotation of the display so that
- // when the display rotation is applied later as a part of the per-window transform, we
- // get the expected screen coordinates. When pointer capture is enabled, we do not apply any
- // rotations and report values directly from the input device.
- if (!isOrientedDevice && mDisplayId && mParameters.mode != Parameters::Mode::POINTER_RELATIVE) {
- if (auto viewport = config.getDisplayViewportById(*mDisplayId); viewport) {
- mOrientation = getInverseRotation(viewport->orientation);
- }
+ mOrientation = (mParameters.orientationAware && mParameters.hasAssociatedDisplay) ||
+ mParameters.mode == Parameters::Mode::POINTER_RELATIVE
+ ? ui::ROTATION_0
+ : getInverseRotation(resolvedViewport.orientation);
+
+ if (!isBoundsSet) {
+ mBoundsInLogicalDisplay = {static_cast<float>(resolvedViewport.logicalLeft),
+ static_cast<float>(resolvedViewport.logicalTop),
+ static_cast<float>(resolvedViewport.logicalRight - 1),
+ static_cast<float>(resolvedViewport.logicalBottom - 1)};
}
bumpGeneration();
diff --git a/services/inputflinger/reader/mapper/CursorInputMapper.h b/services/inputflinger/reader/mapper/CursorInputMapper.h
index b879bfd..e6523a5 100644
--- a/services/inputflinger/reader/mapper/CursorInputMapper.h
+++ b/services/inputflinger/reader/mapper/CursorInputMapper.h
@@ -119,7 +119,8 @@
// ADISPLAY_ID_NONE to target the focused display. If there is no display target (i.e.
// std::nullopt), all events will be ignored.
std::optional<int32_t> mDisplayId;
- ui::Rotation mOrientation;
+ ui::Rotation mOrientation{ui::ROTATION_0};
+ FloatRect mBoundsInLogicalDisplay{};
std::shared_ptr<PointerControllerInterface> mPointerController;
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.cpp b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
index 41c98ef..305685d 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.cpp
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.cpp
@@ -261,4 +261,17 @@
mStylusGestureNotified = deviceId;
}
+std::optional<DisplayViewport> FakeInputReaderPolicy::getViewportForPointerDevice(
+ int32_t associatedDisplayId) {
+ if (associatedDisplayId == ADISPLAY_ID_NONE) {
+ associatedDisplayId = mConfig.defaultPointerDisplayId;
+ }
+ for (auto& viewport : mViewports) {
+ if (viewport.displayId == associatedDisplayId) {
+ return std::make_optional(viewport);
+ }
+ }
+ return std::nullopt;
+}
+
} // namespace android
diff --git a/services/inputflinger/tests/FakeInputReaderPolicy.h b/services/inputflinger/tests/FakeInputReaderPolicy.h
index 48912a6..084aae1 100644
--- a/services/inputflinger/tests/FakeInputReaderPolicy.h
+++ b/services/inputflinger/tests/FakeInputReaderPolicy.h
@@ -79,6 +79,8 @@
void setStylusPointerIconEnabled(bool enabled);
void setIsInputMethodConnectionActive(bool active);
bool isInputMethodConnectionActive() override;
+ std::optional<DisplayViewport> getViewportForPointerDevice(
+ int32_t associatedDisplayId) override;
private:
void getReaderConfiguration(InputReaderConfiguration* outConfig) override;
diff --git a/services/inputflinger/tests/FakePointerController.h b/services/inputflinger/tests/FakePointerController.h
index c374267..c75f6ed 100644
--- a/services/inputflinger/tests/FakePointerController.h
+++ b/services/inputflinger/tests/FakePointerController.h
@@ -40,6 +40,7 @@
bool isPointerShown();
private:
+ std::string dump() override { return ""; }
std::optional<FloatRect> getBounds() const override;
void move(float deltaX, float deltaY) override;
void fade(Transition) override;
@@ -52,7 +53,7 @@
bool mHaveBounds{false};
float mMinX{0}, mMinY{0}, mMaxX{0}, mMaxY{0};
float mX{0}, mY{0};
- int32_t mDisplayId{ADISPLAY_ID_DEFAULT};
+ int32_t mDisplayId{ADISPLAY_ID_NONE};
bool mIsPointerShown{false};
std::map<int32_t, std::vector<int32_t>> mSpotsByDisplay;
diff --git a/services/inputflinger/tests/InputReader_test.cpp b/services/inputflinger/tests/InputReader_test.cpp
index 64ae9e8..53a55f8 100644
--- a/services/inputflinger/tests/InputReader_test.cpp
+++ b/services/inputflinger/tests/InputReader_test.cpp
@@ -37,6 +37,7 @@
#include <UinputDevice.h>
#include <VibratorInputMapper.h>
#include <android-base/thread_annotations.h>
+#include <com_android_input_flags.h>
#include <ftl/enum.h>
#include <gtest/gtest.h>
#include <gui/constants.h>
@@ -99,6 +100,8 @@
// Maximum smoothing time delta so that we don't generate events too far into the future.
constexpr static nsecs_t MAX_BLUETOOTH_SMOOTHING_DELTA = ms2ns(32);
+namespace input_flags = com::android::input::flags;
+
template<typename T>
static inline T min(T a, T b) {
return a < b ? a : b;
@@ -4097,9 +4100,9 @@
ASSERT_EQ(POLICY_FLAG_WAKE, args.policyFlags);
}
-// --- CursorInputMapperTest ---
+// --- CursorInputMapperTestBase ---
-class CursorInputMapperTest : public InputMapperTest {
+class CursorInputMapperTestBase : public InputMapperTest {
protected:
static const int32_t TRACKBALL_MOVEMENT_THRESHOLD;
@@ -4133,11 +4136,11 @@
}
};
-const int32_t CursorInputMapperTest::TRACKBALL_MOVEMENT_THRESHOLD = 6;
+const int32_t CursorInputMapperTestBase::TRACKBALL_MOVEMENT_THRESHOLD = 6;
-void CursorInputMapperTest::testMotionRotation(CursorInputMapper& mapper, int32_t originalX,
- int32_t originalY, int32_t rotatedX,
- int32_t rotatedY) {
+void CursorInputMapperTestBase::testMotionRotation(CursorInputMapper& mapper, int32_t originalX,
+ int32_t originalY, int32_t rotatedX,
+ int32_t rotatedY) {
NotifyMotionArgs args;
process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, originalX);
@@ -4151,6 +4154,16 @@
float(rotatedY) / TRACKBALL_MOVEMENT_THRESHOLD, 0.0f));
}
+// --- CursorInputMapperTest ---
+
+class CursorInputMapperTest : public CursorInputMapperTestBase {
+protected:
+ void SetUp() override {
+ input_flags::enable_pointer_choreographer(false);
+ CursorInputMapperTestBase::SetUp();
+ }
+};
+
TEST_F(CursorInputMapperTest, WhenModeIsPointer_GetSources_ReturnsMouse) {
addConfigurationProperty("cursor.mode", "pointer");
CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
@@ -4180,6 +4193,7 @@
// When the bounds are set, then there should be a valid motion range.
mFakePointerController->setBounds(1, 2, 800 - 1, 480 - 1);
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
InputDeviceInfo info2;
mapper.populateDeviceInfo(info2);
@@ -4906,11 +4920,459 @@
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasNotCalled());
}
-// --- BluetoothCursorInputMapperTest ---
+// --- CursorInputMapperTestWithChoreographer ---
-class BluetoothCursorInputMapperTest : public CursorInputMapperTest {
+class CursorInputMapperTestWithChoreographer : public CursorInputMapperTestBase {
protected:
void SetUp() override {
+ input_flags::enable_pointer_choreographer(true);
+ CursorInputMapperTestBase::SetUp();
+ }
+};
+
+TEST_F(CursorInputMapperTestWithChoreographer, PopulateDeviceInfo_ReturnsRangeFromPolicy) {
+ addConfigurationProperty("cursor.mode", "pointer");
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ InputDeviceInfo info;
+ mapper.populateDeviceInfo(info);
+
+ // Initially there may not be a valid motion range.
+ ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE));
+ ASSERT_EQ(nullptr, info.getMotionRange(AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE));
+ ASSERT_NO_FATAL_FAILURE(assertMotionRange(info, AINPUT_MOTION_RANGE_PRESSURE,
+ AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
+
+ // When the viewport and the default pointer display ID is set, then there should be a valid
+ // motion range.
+ mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+ mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT, ui::ROTATION_0,
+ /*isActive=*/true, "local:0", NO_PORT, ViewportType::INTERNAL);
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+
+ InputDeviceInfo info2;
+ mapper.populateDeviceInfo(info2);
+
+ ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_X, AINPUT_SOURCE_MOUSE, 0,
+ DISPLAY_WIDTH - 1, 0.0f, 0.0f));
+ ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_Y, AINPUT_SOURCE_MOUSE, 0,
+ DISPLAY_HEIGHT - 1, 0.0f, 0.0f));
+ ASSERT_NO_FATAL_FAILURE(assertMotionRange(info2, AINPUT_MOTION_RANGE_PRESSURE,
+ AINPUT_SOURCE_MOUSE, 0.0f, 1.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTestWithChoreographer, Process_ShouldHandleAllButtonsWithZeroCoords) {
+ addConfigurationProperty("cursor.mode", "pointer");
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+ prepareDisplay(ui::ROTATION_0);
+
+ mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+ mFakePointerController->setPosition(100, 200);
+
+ NotifyMotionArgs motionArgs;
+ NotifyKeyArgs keyArgs;
+
+ // press BTN_LEFT, release BTN_LEFT
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 1);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_PRIMARY, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
+
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_LEFT, 0);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ // press BTN_RIGHT + BTN_MIDDLE, release BTN_RIGHT, release BTN_MIDDLE
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 1);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 1);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
+ motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_SECONDARY | AMOTION_EVENT_BUTTON_TERTIARY,
+ motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
+
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_RIGHT, 0);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_TERTIARY, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 1.0f));
+
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_MIDDLE, 0);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_UP, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ // press BTN_BACK, release BTN_BACK
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 1);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_BACK, 0);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.buttonState);
+
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
+
+ // press BTN_SIDE, release BTN_SIDE
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 1);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_BACK, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_SIDE, 0);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_BACK, keyArgs.keyCode);
+
+ // press BTN_FORWARD, release BTN_FORWARD
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 1);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_FORWARD, 0);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
+
+ // press BTN_EXTRA, release BTN_EXTRA
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 1);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_PRESS, motionArgs.action);
+ ASSERT_EQ(AMOTION_EVENT_BUTTON_FORWARD, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_KEY, BTN_EXTRA, 0);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_BUTTON_RELEASE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&motionArgs));
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, motionArgs.action);
+ ASSERT_EQ(0, motionArgs.buttonState);
+ ASSERT_NO_FATAL_FAILURE(
+ assertCursorPointerCoords(motionArgs.pointerCoords[0], 0.0f, 0.0f, 0.0f));
+
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyKeyWasCalled(&keyArgs));
+ ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
+ ASSERT_EQ(AKEYCODE_FORWARD, keyArgs.keyCode);
+}
+
+TEST_F(CursorInputMapperTestWithChoreographer, Process_WhenModeIsPointer_ShouldKeepZeroCoords) {
+ addConfigurationProperty("cursor.mode", "pointer");
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+ prepareDisplay(ui::ROTATION_0);
+
+ mFakePointerController->setBounds(0, 0, 800 - 1, 480 - 1);
+ mFakePointerController->setPosition(100, 200);
+
+ NotifyMotionArgs args;
+
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
+ ASSERT_NO_FATAL_FAILURE(assertPointerCoords(args.pointerCoords[0], 0.0f, 0.0f, 0.0f, 0.0f, 0.0f,
+ 0.0f, 0.0f, 0.0f, 0.0f, 0.0f));
+}
+
+TEST_F(CursorInputMapperTestWithChoreographer, PointerCaptureDisablesVelocityProcessing) {
+ addConfigurationProperty("cursor.mode", "pointer");
+ const VelocityControlParameters testParams(/*scale=*/5.f, /*low threshold=*/0.f,
+ /*high threshold=*/100.f, /*acceleration=*/10.f);
+ mFakePolicy->setVelocityControlParams(testParams);
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+ prepareDisplay(ui::ROTATION_0);
+
+ NotifyDeviceResetArgs resetArgs;
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
+ ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
+ ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
+
+ NotifyMotionArgs args;
+
+ // Move and verify scale is applied.
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AINPUT_SOURCE_MOUSE, args.source);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_HOVER_MOVE, args.action);
+ const float relX = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+ const float relY = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+ ASSERT_GT(relX, 10);
+ ASSERT_GT(relY, 20);
+
+ // Enable Pointer Capture
+ mFakePolicy->setPointerCapture(true);
+ configureDevice(InputReaderConfiguration::Change::POINTER_CAPTURE);
+ NotifyPointerCaptureChangedArgs captureArgs;
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyCaptureWasCalled(&captureArgs));
+ ASSERT_TRUE(captureArgs.request.enable);
+
+ // Move and verify scale is not applied.
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(&args));
+ ASSERT_EQ(AINPUT_SOURCE_MOUSE_RELATIVE, args.source);
+ ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
+ const float relX2 = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_X);
+ const float relY2 = args.pointerCoords[0].getAxisValue(AMOTION_EVENT_AXIS_RELATIVE_Y);
+ ASSERT_EQ(10, relX2);
+ ASSERT_EQ(20, relY2);
+}
+
+TEST_F(CursorInputMapperTestWithChoreographer, ConfigureDisplayId_NoAssociatedViewport) {
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ // Set up the default display.
+ prepareDisplay(ui::ROTATION_90);
+
+ // Set up the secondary display as the display on which the pointer should be shown.
+ // The InputDevice is not associated with any display.
+ prepareSecondaryDisplay();
+ mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+
+ mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
+ mFakePointerController->setPosition(100, 200);
+
+ // Ensure input events are generated without display ID and coords,
+ // because they will be decided later by PointerChoreographer.
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(ADISPLAY_ID_NONE),
+ WithCoords(0.0f, 0.0f))));
+}
+
+TEST_F(CursorInputMapperTestWithChoreographer, ConfigureDisplayId_WithAssociatedViewport) {
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ // Set up the default display.
+ prepareDisplay(ui::ROTATION_90);
+
+ // Set up the secondary display as the display on which the pointer should be shown,
+ // and associate the InputDevice with the secondary display.
+ prepareSecondaryDisplay();
+ mFakePolicy->setDefaultPointerDisplayId(SECONDARY_DISPLAY_ID);
+ mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID);
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+
+ mFakePointerController->setBounds(0, 0, DISPLAY_WIDTH - 1, DISPLAY_HEIGHT - 1);
+ mFakePointerController->setPosition(100, 200);
+
+ // Ensure input events are generated with associated display ID but not with coords,
+ // because the coords will be decided later by PointerChoreographer.
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID),
+ WithCoords(0.0f, 0.0f))));
+}
+
+TEST_F(CursorInputMapperTestWithChoreographer,
+ ConfigureDisplayId_ShouldGenerateEventWithMismatchedPointerDisplay) {
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ // Set up the default display as the display on which the pointer should be shown.
+ prepareDisplay(ui::ROTATION_90);
+ mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+
+ // Associate the InputDevice with the secondary display.
+ prepareSecondaryDisplay();
+ mFakePolicy->addInputUniqueIdAssociation(DEVICE_LOCATION, SECONDARY_DISPLAY_UNIQUE_ID);
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+
+ // With PointerChoreographer enabled, there could be a PointerController for the associated
+ // display even if it is different from the pointer display. So the mapper should generate an
+ // event.
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 10);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_Y, 20);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithSource(AINPUT_SOURCE_MOUSE), WithDisplayId(SECONDARY_DISPLAY_ID),
+ WithCoords(0.0f, 0.0f))));
+}
+
+// --- BluetoothCursorInputMapperTest ---
+
+class BluetoothCursorInputMapperTest : public CursorInputMapperTestBase {
+protected:
+ void SetUp() override {
+ input_flags::enable_pointer_choreographer(false);
InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH);
mFakePointerController = std::make_shared<FakePointerController>();
@@ -5006,6 +5468,122 @@
WithEventTime(expectedEventTime))));
}
+// --- BluetoothCursorInputMapperTestWithChoreographer ---
+
+class BluetoothCursorInputMapperTestWithChoreographer : public CursorInputMapperTestBase {
+protected:
+ void SetUp() override {
+ input_flags::enable_pointer_choreographer(true);
+ InputMapperTest::SetUp(DEVICE_CLASSES | InputDeviceClass::EXTERNAL, BUS_BLUETOOTH);
+
+ mFakePointerController = std::make_shared<FakePointerController>();
+ mFakePolicy->setPointerController(mFakePointerController);
+ }
+};
+
+TEST_F(BluetoothCursorInputMapperTestWithChoreographer, TimestampSmoothening) {
+ addConfigurationProperty("cursor.mode", "pointer");
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ // Set up the default display.
+ prepareDisplay(ui::ROTATION_0);
+ mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+
+ nsecs_t kernelEventTime = ARBITRARY_TIME;
+ nsecs_t expectedEventTime = ARBITRARY_TIME;
+ process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
+ process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithEventTime(expectedEventTime))));
+
+ // Process several events that come in quick succession, according to their timestamps.
+ for (int i = 0; i < 3; i++) {
+ constexpr static nsecs_t delta = ms2ns(1);
+ static_assert(delta < MIN_BLUETOOTH_TIMESTAMP_DELTA);
+ kernelEventTime += delta;
+ expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
+
+ process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
+ process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithEventTime(expectedEventTime))));
+ }
+}
+
+TEST_F(BluetoothCursorInputMapperTestWithChoreographer, TimestampSmootheningIsCapped) {
+ addConfigurationProperty("cursor.mode", "pointer");
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ // Set up the default display.
+ prepareDisplay(ui::ROTATION_0);
+ mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+
+ nsecs_t expectedEventTime = ARBITRARY_TIME;
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithEventTime(expectedEventTime))));
+
+ // Process several events with the same timestamp from the kernel.
+ // Ensure that we do not generate events too far into the future.
+ constexpr static int32_t numEvents =
+ MAX_BLUETOOTH_SMOOTHING_DELTA / MIN_BLUETOOTH_TIMESTAMP_DELTA;
+ for (int i = 0; i < numEvents; i++) {
+ expectedEventTime += MIN_BLUETOOTH_TIMESTAMP_DELTA;
+
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithEventTime(expectedEventTime))));
+ }
+
+ // By processing more events with the same timestamp, we should not generate events with a
+ // timestamp that is more than the specified max time delta from the timestamp at its injection.
+ const nsecs_t cappedEventTime = ARBITRARY_TIME + MAX_BLUETOOTH_SMOOTHING_DELTA;
+ for (int i = 0; i < 3; i++) {
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_REL, REL_X, 1);
+ process(mapper, ARBITRARY_TIME, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithEventTime(cappedEventTime))));
+ }
+}
+
+TEST_F(BluetoothCursorInputMapperTestWithChoreographer, TimestampSmootheningNotUsed) {
+ addConfigurationProperty("cursor.mode", "pointer");
+ CursorInputMapper& mapper = constructAndAddMapper<CursorInputMapper>();
+
+ // Set up the default display.
+ prepareDisplay(ui::ROTATION_0);
+ mFakePolicy->setDefaultPointerDisplayId(DISPLAY_ID);
+ configureDevice(InputReaderConfiguration::Change::DISPLAY_INFO);
+
+ nsecs_t kernelEventTime = ARBITRARY_TIME;
+ nsecs_t expectedEventTime = ARBITRARY_TIME;
+ process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
+ process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithEventTime(expectedEventTime))));
+
+ // If the next event has a timestamp that is sufficiently spaced out so that Bluetooth timestamp
+ // smoothening is not needed, its timestamp is not affected.
+ kernelEventTime += MAX_BLUETOOTH_SMOOTHING_DELTA + ms2ns(1);
+ expectedEventTime = kernelEventTime;
+
+ process(mapper, kernelEventTime, READ_TIME, EV_REL, REL_X, 1);
+ process(mapper, kernelEventTime, READ_TIME, EV_SYN, SYN_REPORT, 0);
+ ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyMotionWasCalled(
+ AllOf(WithMotionAction(AMOTION_EVENT_ACTION_HOVER_MOVE),
+ WithEventTime(expectedEventTime))));
+}
+
// --- TouchInputMapperTest ---
class TouchInputMapperTest : public InputMapperTest {
diff --git a/services/inputflinger/tests/PointerChoreographer_test.cpp b/services/inputflinger/tests/PointerChoreographer_test.cpp
index 7237424..84673a6 100644
--- a/services/inputflinger/tests/PointerChoreographer_test.cpp
+++ b/services/inputflinger/tests/PointerChoreographer_test.cpp
@@ -19,24 +19,99 @@
#include <gtest/gtest.h>
#include <vector>
+#include "FakePointerController.h"
+#include "NotifyArgsBuilders.h"
+#include "TestEventMatchers.h"
#include "TestInputListener.h"
namespace android {
+using ControllerType = PointerControllerInterface::ControllerType;
+
// Helpers to std::visit with lambdas.
template <typename... V>
struct Visitor : V... {};
template <typename... V>
Visitor(V...) -> Visitor<V...>;
+constexpr int32_t DEVICE_ID = 3;
+constexpr int32_t DISPLAY_ID = 5;
+constexpr int32_t ANOTHER_DISPLAY_ID = 10;
+
+static InputDeviceInfo generateTestDeviceInfo(int32_t deviceId, uint32_t source,
+ int32_t associatedDisplayId) {
+ InputDeviceIdentifier identifier;
+
+ auto info = InputDeviceInfo();
+ info.initialize(deviceId, /*generation*/ 1, /*controllerNumber*/ 1, identifier, "alias",
+ /*isExternal*/ false, /*hasMic*/ false, associatedDisplayId);
+ info.addSource(source);
+ return info;
+}
+
+static std::vector<DisplayViewport> createViewports(std::vector<int32_t> displayIds) {
+ std::vector<DisplayViewport> viewports;
+ for (auto displayId : displayIds) {
+ DisplayViewport viewport;
+ viewport.displayId = displayId;
+ viewports.push_back(viewport);
+ }
+ return viewports;
+}
+
// --- PointerChoreographerTest ---
class PointerChoreographerTest : public testing::Test, public PointerChoreographerPolicyInterface {
protected:
TestInputListener mTestListener;
PointerChoreographer mChoreographer{mTestListener, *this};
+ std::list<std::weak_ptr<FakePointerController>> mPointerControllers{};
- std::shared_ptr<PointerControllerInterface> createPointerController() { return {}; }
+ std::shared_ptr<PointerControllerInterface> createPointerController(ControllerType type) {
+ mLastCreatedControllerType = type;
+ std::shared_ptr<FakePointerController> pc = std::make_shared<FakePointerController>();
+ mPointerControllers.emplace_back(pc);
+ return pc;
+ }
+
+ void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) {
+ mPointerDisplayIdNotified = displayId;
+ }
+
+ void assertPointerControllerCreated(ControllerType type) {
+ ASSERT_EQ(type, mLastCreatedControllerType);
+ mLastCreatedControllerType.reset();
+ }
+
+ void assertPointerControllerNotCreated() {
+ ASSERT_EQ(std::nullopt, mLastCreatedControllerType);
+ }
+
+ void assertPointerControllerCount(size_t count) {
+ // At first, erase ones which aren't used anymore.
+ auto it = mPointerControllers.begin();
+ while (it != mPointerControllers.end()) {
+ auto pc = it->lock();
+ if (!pc) {
+ it = mPointerControllers.erase(it);
+ continue;
+ }
+ it++;
+ }
+
+ ASSERT_EQ(count, mPointerControllers.size());
+ }
+
+ void assertPointerDisplayIdNotified(int32_t displayId) {
+ ASSERT_EQ(displayId, mPointerDisplayIdNotified);
+ mPointerDisplayIdNotified.reset();
+ }
+
+ void assertPointerDisplayIdNotNotified() { ASSERT_EQ(std::nullopt, mPointerDisplayIdNotified); }
+
+private:
+ std::optional<ControllerType> mLastCreatedControllerType;
+ std::optional<int32_t> mPointerDisplayIdNotified;
};
TEST_F(PointerChoreographerTest, ForwardsArgsToInnerListener) {
@@ -86,4 +161,247 @@
}
}
+TEST_F(PointerChoreographerTest, WhenMouseIsJustAdded_DoesNotCreatePointerController) {
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ assertPointerControllerNotCreated();
+ assertPointerControllerCount(size_t(0));
+}
+
+TEST_F(PointerChoreographerTest, WhenMouseEventOccurs_CreatesPointerController) {
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20))
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ assertPointerControllerCreated(ControllerType::MOUSE);
+ assertPointerControllerCount(size_t(1));
+}
+
+TEST_F(PointerChoreographerTest, WhenMouseIsRemoved_RemovesPointerController) {
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20))
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ assertPointerControllerCreated(ControllerType::MOUSE);
+ assertPointerControllerCount(size_t(1));
+
+ // Remove the mouse.
+ mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
+ assertPointerControllerCount(size_t(0));
+}
+
+TEST_F(PointerChoreographerTest, WhenKeyboardIsAdded_DoesNotCreatePointerController) {
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0,
+ {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_KEYBOARD, ADISPLAY_ID_NONE)}});
+ assertPointerControllerNotCreated();
+}
+
+TEST_F(PointerChoreographerTest, SetsViewportForAssociatedMouse) {
+ // Just adding a viewport should not create a PointerController.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ assertPointerControllerCount(size_t(0));
+ assertPointerControllerNotCreated();
+
+ // After the mouse emits event, PointerController will be created and viewport will be set.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20))
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ assertPointerControllerCount(size_t(1));
+ assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(DISPLAY_ID, mPointerControllers.back().lock()->getDisplayId());
+}
+
+TEST_F(PointerChoreographerTest, WhenViewportSetLater_SetsViewportForAssociatedMouse) {
+ // Without viewport information, PointerController will be created by a mouse event
+ // but viewport won't be set.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, DISPLAY_ID)}});
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20))
+ .deviceId(DEVICE_ID)
+ .displayId(DISPLAY_ID)
+ .build());
+ assertPointerControllerCount(size_t(1));
+ assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(ADISPLAY_ID_NONE, mPointerControllers.back().lock()->getDisplayId());
+
+ // After Choreographer gets viewport, PointerController should also have viewport.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ ASSERT_EQ(DISPLAY_ID, mPointerControllers.back().lock()->getDisplayId());
+}
+
+TEST_F(PointerChoreographerTest, SetsDefaultMouseViewportForPointerController) {
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+
+ // For a mouse event without a target display, default viewport should be set for
+ // the PointerController.
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20))
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ assertPointerControllerCount(size_t(1));
+ assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(DISPLAY_ID, mPointerControllers.back().lock()->getDisplayId());
+}
+
+TEST_F(PointerChoreographerTest,
+ WhenDefaultMouseDisplayChanges_SetsDefaultMouseViewportForPointerController) {
+ // Set one display as a default mouse display and emit mouse event to create PointerController.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20))
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ assertPointerControllerCount(size_t(1));
+ assertPointerControllerCreated(ControllerType::MOUSE);
+ ASSERT_EQ(DISPLAY_ID, mPointerControllers.back().lock()->getDisplayId());
+
+ // Change default mouse display. Existing PointerController should be removed.
+ mChoreographer.setDefaultMouseDisplayId(ANOTHER_DISPLAY_ID);
+ assertPointerControllerNotCreated();
+ assertPointerControllerCount(size_t(0));
+
+ // New PointerController for the new default display will be created by the motion event.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20))
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ assertPointerControllerCreated(ControllerType::MOUSE);
+ assertPointerControllerCount(size_t(1));
+ ASSERT_EQ(ANOTHER_DISPLAY_ID, mPointerControllers.back().lock()->getDisplayId());
+}
+
+TEST_F(PointerChoreographerTest, CallsNotifyPointerDisplayIdChanged) {
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20))
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+
+ assertPointerDisplayIdNotified(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, WhenViewportIsSetLater_CallsNotifyPointerDisplayIdChanged) {
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20))
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ assertPointerDisplayIdNotNotified();
+
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ assertPointerDisplayIdNotified(DISPLAY_ID);
+}
+
+TEST_F(PointerChoreographerTest, WhenMouseIsRemoved_CallsNotifyPointerDisplayIdChanged) {
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID}));
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20))
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ assertPointerDisplayIdNotified(DISPLAY_ID);
+ assertPointerControllerCount(size_t(1));
+
+ mChoreographer.notifyInputDevicesChanged({/*id=*/1, {}});
+ assertPointerDisplayIdNotified(ADISPLAY_ID_NONE);
+ assertPointerControllerCount(size_t(0));
+}
+
+TEST_F(PointerChoreographerTest,
+ WhenDefaultMouseDisplayChanges_CallsNotifyPointerDisplayIdChanged) {
+ // Add two viewports.
+ mChoreographer.setDisplayViewports(createViewports({DISPLAY_ID, ANOTHER_DISPLAY_ID}));
+
+ // Set one viewport as a default mouse display ID.
+ mChoreographer.setDefaultMouseDisplayId(DISPLAY_ID);
+ mChoreographer.notifyInputDevicesChanged(
+ {/*id=*/0, {generateTestDeviceInfo(DEVICE_ID, AINPUT_SOURCE_MOUSE, ADISPLAY_ID_NONE)}});
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20))
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ assertPointerDisplayIdNotified(DISPLAY_ID);
+
+ // Set another viewport as a default mouse display ID. ADISPLAY_ID_NONE will be notified
+ // before a mouse event.
+ mChoreographer.setDefaultMouseDisplayId(ANOTHER_DISPLAY_ID);
+ assertPointerDisplayIdNotified(ADISPLAY_ID_NONE);
+
+ // After a mouse event, pointer display ID will be notified with new default mouse display.
+ mChoreographer.notifyMotion(
+ MotionArgsBuilder(AMOTION_EVENT_ACTION_HOVER_MOVE, AINPUT_SOURCE_MOUSE)
+ .pointer(PointerBuilder(/*id=*/0, ToolType::MOUSE)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_X, 10)
+ .axis(AMOTION_EVENT_AXIS_RELATIVE_Y, 20))
+ .deviceId(DEVICE_ID)
+ .displayId(ADISPLAY_ID_NONE)
+ .build());
+ assertPointerDisplayIdNotified(ANOTHER_DISPLAY_ID);
+}
+
} // namespace android
diff --git a/services/inputflinger/tests/fuzzers/MapperHelpers.h b/services/inputflinger/tests/fuzzers/MapperHelpers.h
index e1c0fe2..6245a22 100644
--- a/services/inputflinger/tests/fuzzers/MapperHelpers.h
+++ b/services/inputflinger/tests/fuzzers/MapperHelpers.h
@@ -275,6 +275,7 @@
void clearSpots() override {}
int32_t getDisplayId() const override { return mFdp->ConsumeIntegral<int32_t>(); }
void setDisplayViewport(const DisplayViewport& displayViewport) override {}
+ std::string dump() override { return ""; }
};
class FuzzInputReaderPolicy : public InputReaderPolicyInterface {
@@ -309,6 +310,10 @@
void setTouchAffineTransformation(const TouchAffineTransformation t) { mTransform = t; }
void notifyStylusGestureStarted(int32_t, nsecs_t) {}
bool isInputMethodConnectionActive() override { return mFdp->ConsumeBool(); }
+ std::optional<DisplayViewport> getViewportForPointerDevice(
+ int32_t associatedDisplayId) override {
+ return {};
+ }
};
class FuzzInputListener : public virtual InputListenerInterface {