blob: 74d4f3b99f7fb1409632641547e5fdd398a25e65 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <cinttypes>
#include <memory>
#include <CursorInputMapper.h>
#include <InputDevice.h>
#include <InputMapper.h>
#include <InputReader.h>
#include <InputReaderBase.h>
#include <InputReaderFactory.h>
#include <JoystickInputMapper.h>
#include <KeyboardInputMapper.h>
#include <MultiTouchInputMapper.h>
#include <PeripheralController.h>
#include <SensorInputMapper.h>
#include <SingleTouchInputMapper.h>
#include <SwitchInputMapper.h>
#include <TestInputListener.h>
#include <TouchInputMapper.h>
#include <UinputDevice.h>
#include <VibratorInputMapper.h>
#include <android-base/thread_annotations.h>
#include <gtest/gtest.h>
#include <gui/constants.h>
#include "input/DisplayViewport.h"
#include "input/Input.h"
namespace android {
using namespace ftl::flag_operators;
using std::chrono_literals::operator""ms;
// Timeout for waiting for an expected event
static constexpr std::chrono::duration WAIT_TIMEOUT = 100ms;
// An arbitrary time value.
static constexpr nsecs_t ARBITRARY_TIME = 1234;
static constexpr nsecs_t READ_TIME = 4321;
// Arbitrary display properties.
static constexpr int32_t DISPLAY_ID = 0;
static const std::string DISPLAY_UNIQUE_ID = "local:1";
static constexpr int32_t SECONDARY_DISPLAY_ID = DISPLAY_ID + 1;
static const std::string SECONDARY_DISPLAY_UNIQUE_ID = "local:2";
static constexpr int32_t DISPLAY_WIDTH = 480;
static constexpr int32_t DISPLAY_HEIGHT = 800;
static constexpr int32_t VIRTUAL_DISPLAY_ID = 1;
static constexpr int32_t VIRTUAL_DISPLAY_WIDTH = 400;
static constexpr int32_t VIRTUAL_DISPLAY_HEIGHT = 500;
static const char* VIRTUAL_DISPLAY_UNIQUE_ID = "virtual:1";
static constexpr std::optional<uint8_t> NO_PORT = std::nullopt; // no physical port is specified
static constexpr int32_t FIRST_SLOT = 0;
static constexpr int32_t SECOND_SLOT = 1;
static constexpr int32_t THIRD_SLOT = 2;
static constexpr int32_t INVALID_TRACKING_ID = -1;
static constexpr int32_t FIRST_TRACKING_ID = 0;
static constexpr int32_t SECOND_TRACKING_ID = 1;
static constexpr int32_t THIRD_TRACKING_ID = 2;
static constexpr int32_t DEFAULT_BATTERY = 1;
static constexpr int32_t BATTERY_STATUS = 4;
static constexpr int32_t BATTERY_CAPACITY = 66;
static constexpr int32_t LIGHT_BRIGHTNESS = 0x55000000;
static constexpr int32_t LIGHT_COLOR = 0x7F448866;
static constexpr int32_t LIGHT_PLAYER_ID = 2;
static constexpr int32_t ACTION_POINTER_0_DOWN =
AMOTION_EVENT_ACTION_POINTER_DOWN | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
static constexpr int32_t ACTION_POINTER_0_UP =
AMOTION_EVENT_ACTION_POINTER_UP | (0 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
static constexpr int32_t ACTION_POINTER_1_DOWN =
AMOTION_EVENT_ACTION_POINTER_DOWN | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
static constexpr int32_t ACTION_POINTER_1_UP =
AMOTION_EVENT_ACTION_POINTER_UP | (1 << AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
// Error tolerance for floating point assertions.
static const float EPSILON = 0.001f;
using ::testing::AllOf;
MATCHER_P(WithAction, action, "InputEvent with specified action") {
return arg.action == action;
}
MATCHER_P(WithSource, source, "InputEvent with specified source") {
return arg.source == source;
}
MATCHER_P(WithDisplayId, displayId, "InputEvent with specified displayId") {
return arg.displayId == displayId;
}
MATCHER_P2(WithCoords, x, y, "MotionEvent with specified action") {
return arg.pointerCoords[0].getX() == x && arg.pointerCoords[0].getY();
}
template<typename T>
static inline T min(T a, T b) {
return a < b ? a : b;
}
static inline float avg(float x, float y) {
return (x + y) / 2;
}
// Mapping for light color name and the light color
const std::unordered_map<std::string, LightColor> LIGHT_COLORS = {{"red", LightColor::RED},
{"green", LightColor::GREEN},
{"blue", LightColor::BLUE}};
static int32_t getInverseRotation(int32_t orientation) {
switch (orientation) {
case DISPLAY_ORIENTATION_90:
return DISPLAY_ORIENTATION_270;
case DISPLAY_ORIENTATION_270:
return DISPLAY_ORIENTATION_90;
default:
return orientation;
}
}
static void assertAxisResolution(MultiTouchInputMapper& mapper, int axis, float resolution) {
InputDeviceInfo info;
mapper.populateDeviceInfo(&info);
const InputDeviceInfo::MotionRange* motionRange =
info.getMotionRange(axis, AINPUT_SOURCE_TOUCHSCREEN);
ASSERT_NEAR(motionRange->resolution, resolution, EPSILON);
}
static void assertAxisNotPresent(MultiTouchInputMapper& mapper, int axis) {
InputDeviceInfo info;
mapper.populateDeviceInfo(&info);
const InputDeviceInfo::MotionRange* motionRange =
info.getMotionRange(axis, AINPUT_SOURCE_TOUCHSCREEN);
ASSERT_EQ(nullptr, motionRange);
}
// --- FakePointerController ---
class FakePointerController : public PointerControllerInterface {
bool mHaveBounds;
float mMinX, mMinY, mMaxX, mMaxY;
float mX, mY;
int32_t mButtonState;
int32_t mDisplayId;
public:
FakePointerController() :
mHaveBounds(false), mMinX(0), mMinY(0), mMaxX(0), mMaxY(0), mX(0), mY(0),
mButtonState(0), mDisplayId(ADISPLAY_ID_DEFAULT) {
}
virtual ~FakePointerController() {}
void setBounds(float minX, float minY, float maxX, float maxY) {
mHaveBounds = true;
mMinX = minX;
mMinY = minY;
mMaxX = maxX;
mMaxY = maxY;
}
void setPosition(float x, float y) override {
mX = x;
mY = y;
}
void setButtonState(int32_t buttonState) override { mButtonState = buttonState; }
int32_t getButtonState() const override { return mButtonState; }
void getPosition(float* outX, float* outY) const override {
*outX = mX;
*outY = mY;
}
int32_t getDisplayId() const override { return mDisplayId; }
void setDisplayViewport(const DisplayViewport& viewport) override {
mDisplayId = viewport.displayId;
}
const std::map<int32_t, std::vector<int32_t>>& getSpots() {
return mSpotsByDisplay;
}
private:
bool getBounds(float* outMinX, float* outMinY, float* outMaxX, float* outMaxY) const override {
*outMinX = mMinX;
*outMinY = mMinY;
*outMaxX = mMaxX;
*outMaxY = mMaxY;
return mHaveBounds;
}
void move(float deltaX, float deltaY) override {
mX += deltaX;
if (mX < mMinX) mX = mMinX;
if (mX > mMaxX) mX = mMaxX;
mY += deltaY;
if (mY < mMinY) mY = mMinY;
if (mY > mMaxY) mY = mMaxY;
}
void fade(Transition) override {}
void unfade(Transition) override {}
void setPresentation(Presentation) override {}
void setSpots(const PointerCoords*, const uint32_t*, BitSet32 spotIdBits,
int32_t displayId) override {
std::vector<int32_t> newSpots;
// Add spots for fingers that are down.
for (BitSet32 idBits(spotIdBits); !idBits.isEmpty(); ) {
uint32_t id = idBits.clearFirstMarkedBit();
newSpots.push_back(id);
}
mSpotsByDisplay[displayId] = newSpots;
}
void clearSpots() override {}
std::map<int32_t, std::vector<int32_t>> mSpotsByDisplay;
};
// --- FakeInputReaderPolicy ---
class FakeInputReaderPolicy : public InputReaderPolicyInterface {
std::mutex mLock;
std::condition_variable mDevicesChangedCondition;
InputReaderConfiguration mConfig;
std::shared_ptr<FakePointerController> mPointerController;
std::vector<InputDeviceInfo> mInputDevices GUARDED_BY(mLock);
bool mInputDevicesChanged GUARDED_BY(mLock){false};
std::vector<DisplayViewport> mViewports;
TouchAffineTransformation transform;
protected:
virtual ~FakeInputReaderPolicy() {}
public:
FakeInputReaderPolicy() {
}
void assertInputDevicesChanged() {
waitForInputDevices([](bool devicesChanged) {
if (!devicesChanged) {
FAIL() << "Timed out waiting for notifyInputDevicesChanged() to be called.";
}
});
}
void assertInputDevicesNotChanged() {
waitForInputDevices([](bool devicesChanged) {
if (devicesChanged) {
FAIL() << "Expected notifyInputDevicesChanged() to not be called.";
}
});
}
virtual void clearViewports() {
mViewports.clear();
mConfig.setDisplayViewports(mViewports);
}
std::optional<DisplayViewport> getDisplayViewportByUniqueId(const std::string& uniqueId) const {
return mConfig.getDisplayViewportByUniqueId(uniqueId);
}
std::optional<DisplayViewport> getDisplayViewportByType(ViewportType type) const {
return mConfig.getDisplayViewportByType(type);
}
std::optional<DisplayViewport> getDisplayViewportByPort(uint8_t displayPort) const {
return mConfig.getDisplayViewportByPort(displayPort);
}
void addDisplayViewport(DisplayViewport viewport) {
mViewports.push_back(std::move(viewport));
mConfig.setDisplayViewports(mViewports);
}
void addDisplayViewport(int32_t displayId, int32_t width, int32_t height, int32_t orientation,
bool isActive, const std::string& uniqueId,
std::optional<uint8_t> physicalPort, ViewportType type) {
const bool isRotated =
(orientation == DISPLAY_ORIENTATION_90 || orientation == DISPLAY_ORIENTATION_270);
DisplayViewport v;
v.displayId = displayId;
v.orientation = orientation;
v.logicalLeft = 0;
v.logicalTop = 0;
v.logicalRight = isRotated ? height : width;
v.logicalBottom = isRotated ? width : height;
v.physicalLeft = 0;
v.physicalTop = 0;
v.physicalRight = isRotated ? height : width;
v.physicalBottom = isRotated ? width : height;
v.deviceWidth = isRotated ? height : width;
v.deviceHeight = isRotated ? width : height;
v.isActive = isActive;
v.uniqueId = uniqueId;
v.physicalPort = physicalPort;
v.type = type;
addDisplayViewport(v);
}
bool updateViewport(const DisplayViewport& viewport) {
size_t count = mViewports.size();
for (size_t i = 0; i < count; i++) {
const DisplayViewport& currentViewport = mViewports[i];
if (currentViewport.displayId == viewport.displayId) {
mViewports[i] = viewport;
mConfig.setDisplayViewports(mViewports);
return true;
}
}
// no viewport found.
return false;
}
void addExcludedDeviceName(const std::string& deviceName) {
mConfig.excludedDeviceNames.push_back(deviceName);
}
void addInputPortAssociation(const std::string& inputPort, uint8_t displayPort) {
mConfig.portAssociations.insert({inputPort, displayPort});
}
void addInputUniqueIdAssociation(const std::string& inputUniqueId,
const std::string& displayUniqueId) {
mConfig.uniqueIdAssociations.insert({inputUniqueId, displayUniqueId});
}
void addDisabledDevice(int32_t deviceId) { mConfig.disabledDevices.insert(deviceId); }
void removeDisabledDevice(int32_t deviceId) { mConfig.disabledDevices.erase(deviceId); }
void setPointerController(std::shared_ptr<FakePointerController> controller) {
mPointerController = std::move(controller);
}
const InputReaderConfiguration* getReaderConfiguration() const {
return &mConfig;
}
const std::vector<InputDeviceInfo>& getInputDevices() const {
return mInputDevices;
}
TouchAffineTransformation getTouchAffineTransformation(const std::string& inputDeviceDescriptor,
int32_t surfaceRotation) {
return transform;
}
void setTouchAffineTransformation(const TouchAffineTransformation t) {
transform = t;
}
PointerCaptureRequest setPointerCapture(bool enabled) {
mConfig.pointerCaptureRequest = {enabled, mNextPointerCaptureSequenceNumber++};
return mConfig.pointerCaptureRequest;
}
void setShowTouches(bool enabled) {
mConfig.showTouches = enabled;
}
void setDefaultPointerDisplayId(int32_t pointerDisplayId) {
mConfig.defaultPointerDisplayId = pointerDisplayId;
}
float getPointerGestureMovementSpeedRatio() { return mConfig.pointerGestureMovementSpeedRatio; }
void setVelocityControlParams(const VelocityControlParameters& params) {
mConfig.pointerVelocityControlParameters = params;
mConfig.wheelVelocityControlParameters = params;
}
private:
uint32_t mNextPointerCaptureSequenceNumber = 0;
void getReaderConfiguration(InputReaderConfiguration* outConfig) override {
*outConfig = mConfig;
}
std::shared_ptr<PointerControllerInterface> obtainPointerController(
int32_t /*deviceId*/) override {
return mPointerController;
}
void notifyInputDevicesChanged(const std::vector<InputDeviceInfo>& inputDevices) override {
std::scoped_lock<std::mutex> lock(mLock);
mInputDevices = inputDevices;
mInputDevicesChanged = true;
mDevicesChangedCondition.notify_all();
}
std::shared_ptr<KeyCharacterMap> getKeyboardLayoutOverlay(
const InputDeviceIdentifier&) override {
return nullptr;
}
std::string getDeviceAlias(const InputDeviceIdentifier&) override { return ""; }
void waitForInputDevices(std::function<void(bool)> processDevicesChanged) {
std::unique_lock<std::mutex> lock(mLock);
base::ScopedLockAssertion assumeLocked(mLock);
const bool devicesChanged =
mDevicesChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) {
return mInputDevicesChanged;
});
ASSERT_NO_FATAL_FAILURE(processDevicesChanged(devicesChanged));
mInputDevicesChanged = false;
}
};
// --- FakeEventHub ---
class FakeEventHub : public EventHubInterface {
struct KeyInfo {
int32_t keyCode;
uint32_t flags;
};
struct SensorInfo {
InputDeviceSensorType sensorType;
int32_t sensorDataIndex;
};
struct Device {
InputDeviceIdentifier identifier;
ftl::Flags<InputDeviceClass> classes;
PropertyMap configuration;
KeyedVector<int, RawAbsoluteAxisInfo> absoluteAxes;
KeyedVector<int, bool> relativeAxes;
KeyedVector<int32_t, int32_t> keyCodeStates;
KeyedVector<int32_t, int32_t> scanCodeStates;
KeyedVector<int32_t, int32_t> switchStates;
KeyedVector<int32_t, int32_t> absoluteAxisValue;
KeyedVector<int32_t, KeyInfo> keysByScanCode;
KeyedVector<int32_t, KeyInfo> keysByUsageCode;
KeyedVector<int32_t, bool> leds;
// fake mapping which would normally come from keyCharacterMap
std::unordered_map<int32_t, int32_t> keyCodeMapping;
std::unordered_map<int32_t, SensorInfo> sensorsByAbsCode;
BitArray<MSC_MAX> mscBitmask;
std::vector<VirtualKeyDefinition> virtualKeys;
bool enabled;
status_t enable() {
enabled = true;
return OK;
}
status_t disable() {
enabled = false;
return OK;
}
explicit Device(ftl::Flags<InputDeviceClass> classes) : classes(classes), enabled(true) {}
};
std::mutex mLock;
std::condition_variable mEventsCondition;
KeyedVector<int32_t, Device*> mDevices;
std::vector<std::string> mExcludedDevices;
std::vector<RawEvent> mEvents GUARDED_BY(mLock);
std::unordered_map<int32_t /*deviceId*/, std::vector<TouchVideoFrame>> mVideoFrames;
std::vector<int32_t> mVibrators = {0, 1};
std::unordered_map<int32_t, RawLightInfo> mRawLightInfos;
// Simulates a device light brightness, from light id to light brightness.
std::unordered_map<int32_t /* lightId */, int32_t /* brightness*/> mLightBrightness;
// Simulates a device light intensities, from light id to light intensities map.
std::unordered_map<int32_t /* lightId */, std::unordered_map<LightColor, int32_t>>
mLightIntensities;
public:
virtual ~FakeEventHub() {
for (size_t i = 0; i < mDevices.size(); i++) {
delete mDevices.valueAt(i);
}
}
FakeEventHub() { }
void addDevice(int32_t deviceId, const std::string& name,
ftl::Flags<InputDeviceClass> classes) {
Device* device = new Device(classes);
device->identifier.name = name;
mDevices.add(deviceId, device);
enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_ADDED, 0, 0);
}
void removeDevice(int32_t deviceId) {
delete mDevices.valueFor(deviceId);
mDevices.removeItem(deviceId);
enqueueEvent(ARBITRARY_TIME, READ_TIME, deviceId, EventHubInterface::DEVICE_REMOVED, 0, 0);
}
bool isDeviceEnabled(int32_t deviceId) {
Device* device = getDevice(deviceId);
if (device == nullptr) {
ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__);
return false;
}
return device->enabled;
}
status_t enableDevice(int32_t deviceId) {
status_t result;
Device* device = getDevice(deviceId);
if (device == nullptr) {
ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__);
return BAD_VALUE;
}
if (device->enabled) {
ALOGW("Duplicate call to %s, device %" PRId32 " already enabled", __func__, deviceId);
return OK;
}
result = device->enable();
return result;
}
status_t disableDevice(int32_t deviceId) {
Device* device = getDevice(deviceId);
if (device == nullptr) {
ALOGE("Incorrect device id=%" PRId32 " provided to %s", deviceId, __func__);
return BAD_VALUE;
}
if (!device->enabled) {
ALOGW("Duplicate call to %s, device %" PRId32 " already disabled", __func__, deviceId);
return OK;
}
return device->disable();
}
void finishDeviceScan() {
enqueueEvent(ARBITRARY_TIME, READ_TIME, 0, EventHubInterface::FINISHED_DEVICE_SCAN, 0, 0);
}
void addConfigurationProperty(int32_t deviceId, const String8& key, const String8& value) {
Device* device = getDevice(deviceId);
device->configuration.addProperty(key, value);
}
void addConfigurationMap(int32_t deviceId, const PropertyMap* configuration) {
Device* device = getDevice(deviceId);
device->configuration.addAll(configuration);
}
void addAbsoluteAxis(int32_t deviceId, int axis,
int32_t minValue, int32_t maxValue, int flat, int fuzz, int resolution = 0) {
Device* device = getDevice(deviceId);
RawAbsoluteAxisInfo info;
info.valid = true;
info.minValue = minValue;
info.maxValue = maxValue;
info.flat = flat;
info.fuzz = fuzz;
info.resolution = resolution;
device->absoluteAxes.add(axis, info);
}
void addRelativeAxis(int32_t deviceId, int32_t axis) {
Device* device = getDevice(deviceId);
device->relativeAxes.add(axis, true);
}
void setKeyCodeState(int32_t deviceId, int32_t keyCode, int32_t state) {
Device* device = getDevice(deviceId);
device->keyCodeStates.replaceValueFor(keyCode, state);
}
void setScanCodeState(int32_t deviceId, int32_t scanCode, int32_t state) {
Device* device = getDevice(deviceId);
device->scanCodeStates.replaceValueFor(scanCode, state);
}
void setSwitchState(int32_t deviceId, int32_t switchCode, int32_t state) {
Device* device = getDevice(deviceId);
device->switchStates.replaceValueFor(switchCode, state);
}
void setAbsoluteAxisValue(int32_t deviceId, int32_t axis, int32_t value) {
Device* device = getDevice(deviceId);
device->absoluteAxisValue.replaceValueFor(axis, value);
}
void addKey(int32_t deviceId, int32_t scanCode, int32_t usageCode,
int32_t keyCode, uint32_t flags) {
Device* device = getDevice(deviceId);
KeyInfo info;
info.keyCode = keyCode;
info.flags = flags;
if (scanCode) {
device->keysByScanCode.add(scanCode, info);
}
if (usageCode) {
device->keysByUsageCode.add(usageCode, info);
}
}
void addKeyCodeMapping(int32_t deviceId, int32_t fromKeyCode, int32_t toKeyCode) {
Device* device = getDevice(deviceId);
device->keyCodeMapping.insert_or_assign(fromKeyCode, toKeyCode);
}
void addLed(int32_t deviceId, int32_t led, bool initialState) {
Device* device = getDevice(deviceId);
device->leds.add(led, initialState);
}
void addSensorAxis(int32_t deviceId, int32_t absCode, InputDeviceSensorType sensorType,
int32_t sensorDataIndex) {
Device* device = getDevice(deviceId);
SensorInfo info;
info.sensorType = sensorType;
info.sensorDataIndex = sensorDataIndex;
device->sensorsByAbsCode.emplace(absCode, info);
}
void setMscEvent(int32_t deviceId, int32_t mscEvent) {
Device* device = getDevice(deviceId);
typename BitArray<MSC_MAX>::Buffer buffer;
buffer[mscEvent / 32] = 1 << mscEvent % 32;
device->mscBitmask.loadFromBuffer(buffer);
}
void addRawLightInfo(int32_t rawId, RawLightInfo&& info) {
mRawLightInfos.emplace(rawId, std::move(info));
}
void fakeLightBrightness(int32_t rawId, int32_t brightness) {
mLightBrightness.emplace(rawId, brightness);
}
void fakeLightIntensities(int32_t rawId,
const std::unordered_map<LightColor, int32_t> intensities) {
mLightIntensities.emplace(rawId, std::move(intensities));
}
bool getLedState(int32_t deviceId, int32_t led) {
Device* device = getDevice(deviceId);
return device->leds.valueFor(led);
}
std::vector<std::string>& getExcludedDevices() {
return mExcludedDevices;
}
void addVirtualKeyDefinition(int32_t deviceId, const VirtualKeyDefinition& definition) {
Device* device = getDevice(deviceId);
device->virtualKeys.push_back(definition);
}
void enqueueEvent(nsecs_t when, nsecs_t readTime, int32_t deviceId, int32_t type, int32_t code,
int32_t value) {
std::scoped_lock<std::mutex> lock(mLock);
RawEvent event;
event.when = when;
event.readTime = readTime;
event.deviceId = deviceId;
event.type = type;
event.code = code;
event.value = value;
mEvents.push_back(event);
if (type == EV_ABS) {
setAbsoluteAxisValue(deviceId, code, value);
}
}
void setVideoFrames(std::unordered_map<int32_t /*deviceId*/,
std::vector<TouchVideoFrame>> videoFrames) {
mVideoFrames = std::move(videoFrames);
}
void assertQueueIsEmpty() {
std::unique_lock<std::mutex> lock(mLock);
base::ScopedLockAssertion assumeLocked(mLock);
const bool queueIsEmpty =
mEventsCondition.wait_for(lock, WAIT_TIMEOUT,
[this]() REQUIRES(mLock) { return mEvents.size() == 0; });
if (!queueIsEmpty) {
FAIL() << "Timed out waiting for EventHub queue to be emptied.";
}
}
private:
Device* getDevice(int32_t deviceId) const {
ssize_t index = mDevices.indexOfKey(deviceId);
return index >= 0 ? mDevices.valueAt(index) : nullptr;
}
ftl::Flags<InputDeviceClass> getDeviceClasses(int32_t deviceId) const override {
Device* device = getDevice(deviceId);
return device ? device->classes : ftl::Flags<InputDeviceClass>(0);
}
InputDeviceIdentifier getDeviceIdentifier(int32_t deviceId) const override {
Device* device = getDevice(deviceId);
return device ? device->identifier : InputDeviceIdentifier();
}
int32_t getDeviceControllerNumber(int32_t) const override { return 0; }
void getConfiguration(int32_t deviceId, PropertyMap* outConfiguration) const override {
Device* device = getDevice(deviceId);
if (device) {
*outConfiguration = device->configuration;
}
}
status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
RawAbsoluteAxisInfo* outAxisInfo) const override {
Device* device = getDevice(deviceId);
if (device && device->enabled) {
ssize_t index = device->absoluteAxes.indexOfKey(axis);
if (index >= 0) {
*outAxisInfo = device->absoluteAxes.valueAt(index);
return OK;
}
}
outAxisInfo->clear();
return -1;
}
bool hasRelativeAxis(int32_t deviceId, int axis) const override {
Device* device = getDevice(deviceId);
if (device) {
return device->relativeAxes.indexOfKey(axis) >= 0;
}
return false;
}
bool hasInputProperty(int32_t, int) const override { return false; }
bool hasMscEvent(int32_t deviceId, int mscEvent) const override final {
Device* device = getDevice(deviceId);
if (device) {
return mscEvent >= 0 && mscEvent <= MSC_MAX ? device->mscBitmask.test(mscEvent) : false;
}
return false;
}
status_t mapKey(int32_t deviceId, int32_t scanCode, int32_t usageCode, int32_t metaState,
int32_t* outKeycode, int32_t* outMetaState, uint32_t* outFlags) const override {
Device* device = getDevice(deviceId);
if (device) {
const KeyInfo* key = getKey(device, scanCode, usageCode);
if (key) {
if (outKeycode) {
*outKeycode = key->keyCode;
}
if (outFlags) {
*outFlags = key->flags;
}
if (outMetaState) {
*outMetaState = metaState;
}
return OK;
}
}
return NAME_NOT_FOUND;
}
const KeyInfo* getKey(Device* device, int32_t scanCode, int32_t usageCode) const {
if (usageCode) {
ssize_t index = device->keysByUsageCode.indexOfKey(usageCode);
if (index >= 0) {
return &device->keysByUsageCode.valueAt(index);
}
}
if (scanCode) {
ssize_t index = device->keysByScanCode.indexOfKey(scanCode);
if (index >= 0) {
return &device->keysByScanCode.valueAt(index);
}
}
return nullptr;
}
status_t mapAxis(int32_t, int32_t, AxisInfo*) const override { return NAME_NOT_FOUND; }
base::Result<std::pair<InputDeviceSensorType, int32_t>> mapSensor(int32_t deviceId,
int32_t absCode) {
Device* device = getDevice(deviceId);
if (!device) {
return Errorf("Sensor device not found.");
}
auto it = device->sensorsByAbsCode.find(absCode);
if (it == device->sensorsByAbsCode.end()) {
return Errorf("Sensor map not found.");
}
const SensorInfo& info = it->second;
return std::make_pair(info.sensorType, info.sensorDataIndex);
}
void setExcludedDevices(const std::vector<std::string>& devices) override {
mExcludedDevices = devices;
}
size_t getEvents(int, RawEvent* buffer, size_t bufferSize) override {
std::scoped_lock lock(mLock);
const size_t filledSize = std::min(mEvents.size(), bufferSize);
std::copy(mEvents.begin(), mEvents.begin() + filledSize, buffer);
mEvents.erase(mEvents.begin(), mEvents.begin() + filledSize);
mEventsCondition.notify_all();
return filledSize;
}
std::vector<TouchVideoFrame> getVideoFrames(int32_t deviceId) override {
auto it = mVideoFrames.find(deviceId);
if (it != mVideoFrames.end()) {
std::vector<TouchVideoFrame> frames = std::move(it->second);
mVideoFrames.erase(deviceId);
return frames;
}
return {};
}
int32_t getScanCodeState(int32_t deviceId, int32_t scanCode) const override {
Device* device = getDevice(deviceId);
if (device) {
ssize_t index = device->scanCodeStates.indexOfKey(scanCode);
if (index >= 0) {
return device->scanCodeStates.valueAt(index);
}
}
return AKEY_STATE_UNKNOWN;
}
int32_t getKeyCodeState(int32_t deviceId, int32_t keyCode) const override {
Device* device = getDevice(deviceId);
if (device) {
ssize_t index = device->keyCodeStates.indexOfKey(keyCode);
if (index >= 0) {
return device->keyCodeStates.valueAt(index);
}
}
return AKEY_STATE_UNKNOWN;
}
int32_t getSwitchState(int32_t deviceId, int32_t sw) const override {
Device* device = getDevice(deviceId);
if (device) {
ssize_t index = device->switchStates.indexOfKey(sw);
if (index >= 0) {
return device->switchStates.valueAt(index);
}
}
return AKEY_STATE_UNKNOWN;
}
status_t getAbsoluteAxisValue(int32_t deviceId, int32_t axis,
int32_t* outValue) const override {
Device* device = getDevice(deviceId);
if (device) {
ssize_t index = device->absoluteAxisValue.indexOfKey(axis);
if (index >= 0) {
*outValue = device->absoluteAxisValue.valueAt(index);
return OK;
}
}
*outValue = 0;
return -1;
}
int32_t getKeyCodeForKeyLocation(int32_t deviceId, int32_t locationKeyCode) const override {
Device* device = getDevice(deviceId);
if (!device) {
return AKEYCODE_UNKNOWN;
}
auto it = device->keyCodeMapping.find(locationKeyCode);
return it != device->keyCodeMapping.end() ? it->second : locationKeyCode;
}
// Return true if the device has non-empty key layout.
bool markSupportedKeyCodes(int32_t deviceId, size_t numCodes, const int32_t* keyCodes,
uint8_t* outFlags) const override {
bool result = false;
Device* device = getDevice(deviceId);
if (device) {
result = device->keysByScanCode.size() > 0 || device->keysByUsageCode.size() > 0;
for (size_t i = 0; i < numCodes; i++) {
for (size_t j = 0; j < device->keysByScanCode.size(); j++) {
if (keyCodes[i] == device->keysByScanCode.valueAt(j).keyCode) {
outFlags[i] = 1;
}
}
for (size_t j = 0; j < device->keysByUsageCode.size(); j++) {
if (keyCodes[i] == device->keysByUsageCode.valueAt(j).keyCode) {
outFlags[i] = 1;
}
}
}
}
return result;
}
bool hasScanCode(int32_t deviceId, int32_t scanCode) const override {
Device* device = getDevice(deviceId);
if (device) {
ssize_t index = device->keysByScanCode.indexOfKey(scanCode);
return index >= 0;
}
return false;
}
bool hasKeyCode(int32_t deviceId, int32_t keyCode) const override {
Device* device = getDevice(deviceId);
if (!device) {
return false;
}
for (size_t i = 0; i < device->keysByScanCode.size(); i++) {
if (keyCode == device->keysByScanCode.valueAt(i).keyCode) {
return true;
}
}
for (size_t j = 0; j < device->keysByUsageCode.size(); j++) {
if (keyCode == device->keysByUsageCode.valueAt(j).keyCode) {
return true;
}
}
return false;
}
bool hasLed(int32_t deviceId, int32_t led) const override {
Device* device = getDevice(deviceId);
return device && device->leds.indexOfKey(led) >= 0;
}
void setLedState(int32_t deviceId, int32_t led, bool on) override {
Device* device = getDevice(deviceId);
if (device) {
ssize_t index = device->leds.indexOfKey(led);
if (index >= 0) {
device->leds.replaceValueAt(led, on);
} else {
ADD_FAILURE()
<< "Attempted to set the state of an LED that the EventHub declared "
"was not present. led=" << led;
}
}
}
void getVirtualKeyDefinitions(
int32_t deviceId, std::vector<VirtualKeyDefinition>& outVirtualKeys) const override {
outVirtualKeys.clear();
Device* device = getDevice(deviceId);
if (device) {
outVirtualKeys = device->virtualKeys;
}
}
const std::shared_ptr<KeyCharacterMap> getKeyCharacterMap(int32_t) const override {
return nullptr;
}
bool setKeyboardLayoutOverlay(int32_t, std::shared_ptr<KeyCharacterMap>) override {
return false;
}
void vibrate(int32_t, const VibrationElement&) override {}
void cancelVibrate(int32_t) override {}
std::vector<int32_t> getVibratorIds(int32_t deviceId) override { return mVibrators; };
std::optional<int32_t> getBatteryCapacity(int32_t, int32_t) const override {
return BATTERY_CAPACITY;
}
std::optional<int32_t> getBatteryStatus(int32_t, int32_t) const override {
return BATTERY_STATUS;
}
const std::vector<int32_t> getRawBatteryIds(int32_t deviceId) { return {}; }
std::optional<RawBatteryInfo> getRawBatteryInfo(int32_t deviceId, int32_t batteryId) {
return std::nullopt;
}
const std::vector<int32_t> getRawLightIds(int32_t deviceId) override {
std::vector<int32_t> ids;
for (const auto& [rawId, info] : mRawLightInfos) {
ids.push_back(rawId);
}
return ids;
}
std::optional<RawLightInfo> getRawLightInfo(int32_t deviceId, int32_t lightId) override {
auto it = mRawLightInfos.find(lightId);
if (it == mRawLightInfos.end()) {
return std::nullopt;
}
return it->second;
}
void setLightBrightness(int32_t deviceId, int32_t lightId, int32_t brightness) override {
mLightBrightness.emplace(lightId, brightness);
}
void setLightIntensities(int32_t deviceId, int32_t lightId,
std::unordered_map<LightColor, int32_t> intensities) override {
mLightIntensities.emplace(lightId, intensities);
};
std::optional<int32_t> getLightBrightness(int32_t deviceId, int32_t lightId) override {
auto lightIt = mLightBrightness.find(lightId);
if (lightIt == mLightBrightness.end()) {
return std::nullopt;
}
return lightIt->second;
}
std::optional<std::unordered_map<LightColor, int32_t>> getLightIntensities(
int32_t deviceId, int32_t lightId) override {
auto lightIt = mLightIntensities.find(lightId);
if (lightIt == mLightIntensities.end()) {
return std::nullopt;
}
return lightIt->second;
};
virtual bool isExternal(int32_t) const {
return false;
}
void dump(std::string&) override {}
void monitor() override {}
void requestReopenDevices() override {}
void wake() override {}
};
// --- FakeInputMapper ---
class FakeInputMapper : public InputMapper {
uint32_t mSources;
int32_t mKeyboardType;
int32_t mMetaState;
KeyedVector<int32_t, int32_t> mKeyCodeStates;
KeyedVector<int32_t, int32_t> mScanCodeStates;
KeyedVector<int32_t, int32_t> mSwitchStates;
// fake mapping which would normally come from keyCharacterMap
std::unordered_map<int32_t, int32_t> mKeyCodeMapping;
std::vector<int32_t> mSupportedKeyCodes;
std::mutex mLock;
std::condition_variable mStateChangedCondition;
bool mConfigureWasCalled GUARDED_BY(mLock);
bool mResetWasCalled GUARDED_BY(mLock);
bool mProcessWasCalled GUARDED_BY(mLock);
RawEvent mLastEvent GUARDED_BY(mLock);
std::optional<DisplayViewport> mViewport;
public:
FakeInputMapper(InputDeviceContext& deviceContext, uint32_t sources)
: InputMapper(deviceContext),
mSources(sources),
mKeyboardType(AINPUT_KEYBOARD_TYPE_NONE),
mMetaState(0),
mConfigureWasCalled(false),
mResetWasCalled(false),
mProcessWasCalled(false) {}
virtual ~FakeInputMapper() {}
void setKeyboardType(int32_t keyboardType) {
mKeyboardType = keyboardType;
}
void setMetaState(int32_t metaState) {
mMetaState = metaState;
}
void assertConfigureWasCalled() {
std::unique_lock<std::mutex> lock(mLock);
base::ScopedLockAssertion assumeLocked(mLock);
const bool configureCalled =
mStateChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) {
return mConfigureWasCalled;
});
if (!configureCalled) {
FAIL() << "Expected configure() to have been called.";
}
mConfigureWasCalled = false;
}
void assertResetWasCalled() {
std::unique_lock<std::mutex> lock(mLock);
base::ScopedLockAssertion assumeLocked(mLock);
const bool resetCalled =
mStateChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) {
return mResetWasCalled;
});
if (!resetCalled) {
FAIL() << "Expected reset() to have been called.";
}
mResetWasCalled = false;
}
void assertProcessWasCalled(RawEvent* outLastEvent = nullptr) {
std::unique_lock<std::mutex> lock(mLock);
base::ScopedLockAssertion assumeLocked(mLock);
const bool processCalled =
mStateChangedCondition.wait_for(lock, WAIT_TIMEOUT, [this]() REQUIRES(mLock) {
return mProcessWasCalled;
});
if (!processCalled) {
FAIL() << "Expected process() to have been called.";
}
if (outLastEvent) {
*outLastEvent = mLastEvent;
}
mProcessWasCalled = false;
}
void setKeyCodeState(int32_t keyCode, int32_t state) {
mKeyCodeStates.replaceValueFor(keyCode, state);
}
void setScanCodeState(int32_t scanCode, int32_t state) {
mScanCodeStates.replaceValueFor(scanCode, state);
}
void setSwitchState(int32_t switchCode, int32_t state) {
mSwitchStates.replaceValueFor(switchCode, state);
}
void addSupportedKeyCode(int32_t keyCode) {
mSupportedKeyCodes.push_back(keyCode);
}
void addKeyCodeMapping(int32_t fromKeyCode, int32_t toKeyCode) {
mKeyCodeMapping.insert_or_assign(fromKeyCode, toKeyCode);
}
private:
uint32_t getSources() const override { return mSources; }
void populateDeviceInfo(InputDeviceInfo* deviceInfo) override {
InputMapper::populateDeviceInfo(deviceInfo);
if (mKeyboardType != AINPUT_KEYBOARD_TYPE_NONE) {
deviceInfo->setKeyboardType(mKeyboardType);
}
}
void configure(nsecs_t, const InputReaderConfiguration* config, uint32_t changes) override {
std::scoped_lock<std::mutex> lock(mLock);
mConfigureWasCalled = true;
// Find the associated viewport if exist.
const std::optional<uint8_t> displayPort = getDeviceContext().getAssociatedDisplayPort();
if (displayPort && (changes & InputReaderConfiguration::CHANGE_DISPLAY_INFO)) {
mViewport = config->getDisplayViewportByPort(*displayPort);
}
mStateChangedCondition.notify_all();
}
void reset(nsecs_t) override {
std::scoped_lock<std::mutex> lock(mLock);
mResetWasCalled = true;
mStateChangedCondition.notify_all();
}
void process(const RawEvent* rawEvent) override {
std::scoped_lock<std::mutex> lock(mLock);
mLastEvent = *rawEvent;
mProcessWasCalled = true;
mStateChangedCondition.notify_all();
}
int32_t getKeyCodeState(uint32_t, int32_t keyCode) override {
ssize_t index = mKeyCodeStates.indexOfKey(keyCode);
return index >= 0 ? mKeyCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN;
}
int32_t getKeyCodeForKeyLocation(int32_t locationKeyCode) const override {
auto it = mKeyCodeMapping.find(locationKeyCode);
return it != mKeyCodeMapping.end() ? it->second : locationKeyCode;
}
int32_t getScanCodeState(uint32_t, int32_t scanCode) override {
ssize_t index = mScanCodeStates.indexOfKey(scanCode);
return index >= 0 ? mScanCodeStates.valueAt(index) : AKEY_STATE_UNKNOWN;
}
int32_t getSwitchState(uint32_t, int32_t switchCode) override {
ssize_t index = mSwitchStates.indexOfKey(switchCode);
return index >= 0 ? mSwitchStates.valueAt(index) : AKEY_STATE_UNKNOWN;
}
// Return true if the device has non-empty key layout.
bool markSupportedKeyCodes(uint32_t, size_t numCodes, const int32_t* keyCodes,
uint8_t* outFlags) override {
for (size_t i = 0; i < numCodes; i++) {
for (size_t j = 0; j < mSupportedKeyCodes.size(); j++) {
if (keyCodes[i] == mSupportedKeyCodes[j]) {
outFlags[i] = 1;
}
}
}
bool result = mSupportedKeyCodes.size() > 0;
return result;
}
virtual int32_t getMetaState() {
return mMetaState;
}
virtual void fadePointer() {
}
virtual std::optional<int32_t> getAssociatedDisplay() {
if (mViewport) {
return std::make_optional(mViewport->displayId);
}
return std::nullopt;
}
};
// --- InstrumentedInputReader ---
class InstrumentedInputReader : public InputReader {
std::queue<std::shared_ptr<InputDevice>> mNextDevices;
public:
InstrumentedInputReader(std::shared_ptr<EventHubInterface> eventHub,
const sp<InputReaderPolicyInterface>& policy,
InputListenerInterface& listener)
: InputReader(eventHub, policy, listener), mFakeContext(this) {}
virtual ~InstrumentedInputReader() {}
void pushNextDevice(std::shared_ptr<InputDevice> device) { mNextDevices.push(device); }
std::shared_ptr<InputDevice> newDevice(int32_t deviceId, const std::string& name,
const std::string& location = "") {
InputDeviceIdentifier identifier;
identifier.name = name;
identifier.location = location;
int32_t generation = deviceId + 1;
return std::make_shared<InputDevice>(&mFakeContext, deviceId, generation, identifier);
}
// Make the protected loopOnce method accessible to tests.
using InputReader::loopOnce;
protected:
virtual std::shared_ptr<InputDevice> createDeviceLocked(int32_t eventHubId,
const InputDeviceIdentifier& identifier)
REQUIRES(mLock) {
if (!mNextDevices.empty()) {
std::shared_ptr<InputDevice> device(std::move(mNextDevices.front()));
mNextDevices.pop();
return device;
}
return InputReader::createDeviceLocked(eventHubId, identifier);
}
// --- FakeInputReaderContext ---
class FakeInputReaderContext : public ContextImpl {
int32_t mGlobalMetaState;
bool mUpdateGlobalMetaStateWasCalled;
int32_t mGeneration;
public:
FakeInputReaderContext(InputReader* reader)
: ContextImpl(reader),
mGlobalMetaState(0),
mUpdateGlobalMetaStateWasCalled(false),
mGeneration(1) {}
virtual ~FakeInputReaderContext() {}
void assertUpdateGlobalMetaStateWasCalled() {
ASSERT_TRUE(mUpdateGlobalMetaStateWasCalled)
<< "Expected updateGlobalMetaState() to have been called.";
mUpdateGlobalMetaStateWasCalled = false;
}
void setGlobalMetaState(int32_t state) { mGlobalMetaState = state; }
uint32_t getGeneration() { return mGeneration; }
void updateGlobalMetaState() override {
mUpdateGlobalMetaStateWasCalled = true;
ContextImpl::updateGlobalMetaState();
}
int32_t getGlobalMetaState() override {
return mGlobalMetaState | ContextImpl::getGlobalMetaState();
}
int32_t bumpGeneration() override {
mGeneration = ContextImpl::bumpGeneration();
return mGeneration;
}
} mFakeContext;
friend class InputReaderTest;
public:
FakeInputReaderContext* getContext() { return &mFakeContext; }
};
// --- InputReaderPolicyTest ---
class InputReaderPolicyTest : public testing::Test {
protected:
sp<FakeInputReaderPolicy> mFakePolicy;
void SetUp() override { mFakePolicy = new FakeInputReaderPolicy(); }
void TearDown() override { mFakePolicy.clear(); }
};
/**
* Check that empty set of viewports is an acceptable configuration.
* Also try to get internal viewport two different ways - by type and by uniqueId.
*
* There will be confusion if two viewports with empty uniqueId and identical type are present.
* Such configuration is not currently allowed.
*/
TEST_F(InputReaderPolicyTest, Viewports_GetCleared) {
static const std::string uniqueId = "local:0";
// We didn't add any viewports yet, so there shouldn't be any.
std::optional<DisplayViewport> internalViewport =
mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
ASSERT_FALSE(internalViewport);
// Add an internal viewport, then clear it
mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId, NO_PORT,
ViewportType::INTERNAL);
// Check matching by uniqueId
internalViewport = mFakePolicy->getDisplayViewportByUniqueId(uniqueId);
ASSERT_TRUE(internalViewport);
ASSERT_EQ(ViewportType::INTERNAL, internalViewport->type);
// Check matching by viewport type
internalViewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
ASSERT_TRUE(internalViewport);
ASSERT_EQ(uniqueId, internalViewport->uniqueId);
mFakePolicy->clearViewports();
// Make sure nothing is found after clear
internalViewport = mFakePolicy->getDisplayViewportByUniqueId(uniqueId);
ASSERT_FALSE(internalViewport);
internalViewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
ASSERT_FALSE(internalViewport);
}
TEST_F(InputReaderPolicyTest, Viewports_GetByType) {
const std::string internalUniqueId = "local:0";
const std::string externalUniqueId = "local:1";
const std::string virtualUniqueId1 = "virtual:2";
const std::string virtualUniqueId2 = "virtual:3";
constexpr int32_t virtualDisplayId1 = 2;
constexpr int32_t virtualDisplayId2 = 3;
// Add an internal viewport
mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_ORIENTATION_0, true /*isActive*/, internalUniqueId,
NO_PORT, ViewportType::INTERNAL);
// Add an external viewport
mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_ORIENTATION_0, true /*isActive*/, externalUniqueId,
NO_PORT, ViewportType::EXTERNAL);
// Add an virtual viewport
mFakePolicy->addDisplayViewport(virtualDisplayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_ORIENTATION_0, true /*isActive*/, virtualUniqueId1,
NO_PORT, ViewportType::VIRTUAL);
// Add another virtual viewport
mFakePolicy->addDisplayViewport(virtualDisplayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_ORIENTATION_0, true /*isActive*/, virtualUniqueId2,
NO_PORT, ViewportType::VIRTUAL);
// Check matching by type for internal
std::optional<DisplayViewport> internalViewport =
mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
ASSERT_TRUE(internalViewport);
ASSERT_EQ(internalUniqueId, internalViewport->uniqueId);
// Check matching by type for external
std::optional<DisplayViewport> externalViewport =
mFakePolicy->getDisplayViewportByType(ViewportType::EXTERNAL);
ASSERT_TRUE(externalViewport);
ASSERT_EQ(externalUniqueId, externalViewport->uniqueId);
// Check matching by uniqueId for virtual viewport #1
std::optional<DisplayViewport> virtualViewport1 =
mFakePolicy->getDisplayViewportByUniqueId(virtualUniqueId1);
ASSERT_TRUE(virtualViewport1);
ASSERT_EQ(ViewportType::VIRTUAL, virtualViewport1->type);
ASSERT_EQ(virtualUniqueId1, virtualViewport1->uniqueId);
ASSERT_EQ(virtualDisplayId1, virtualViewport1->displayId);
// Check matching by uniqueId for virtual viewport #2
std::optional<DisplayViewport> virtualViewport2 =
mFakePolicy->getDisplayViewportByUniqueId(virtualUniqueId2);
ASSERT_TRUE(virtualViewport2);
ASSERT_EQ(ViewportType::VIRTUAL, virtualViewport2->type);
ASSERT_EQ(virtualUniqueId2, virtualViewport2->uniqueId);
ASSERT_EQ(virtualDisplayId2, virtualViewport2->displayId);
}
/**
* We can have 2 viewports of the same kind. We can distinguish them by uniqueId, and confirm
* that lookup works by checking display id.
* Check that 2 viewports of each kind is possible, for all existing viewport types.
*/
TEST_F(InputReaderPolicyTest, Viewports_TwoOfSameType) {
const std::string uniqueId1 = "uniqueId1";
const std::string uniqueId2 = "uniqueId2";
constexpr int32_t displayId1 = 2;
constexpr int32_t displayId2 = 3;
std::vector<ViewportType> types = {ViewportType::INTERNAL, ViewportType::EXTERNAL,
ViewportType::VIRTUAL};
for (const ViewportType& type : types) {
mFakePolicy->clearViewports();
// Add a viewport
mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1,
NO_PORT, type);
// Add another viewport
mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2,
NO_PORT, type);
// Check that correct display viewport was returned by comparing the display IDs.
std::optional<DisplayViewport> viewport1 =
mFakePolicy->getDisplayViewportByUniqueId(uniqueId1);
ASSERT_TRUE(viewport1);
ASSERT_EQ(displayId1, viewport1->displayId);
ASSERT_EQ(type, viewport1->type);
std::optional<DisplayViewport> viewport2 =
mFakePolicy->getDisplayViewportByUniqueId(uniqueId2);
ASSERT_TRUE(viewport2);
ASSERT_EQ(displayId2, viewport2->displayId);
ASSERT_EQ(type, viewport2->type);
// When there are multiple viewports of the same kind, and uniqueId is not specified
// in the call to getDisplayViewport, then that situation is not supported.
// The viewports can be stored in any order, so we cannot rely on the order, since that
// is just implementation detail.
// However, we can check that it still returns *a* viewport, we just cannot assert
// which one specifically is returned.
std::optional<DisplayViewport> someViewport = mFakePolicy->getDisplayViewportByType(type);
ASSERT_TRUE(someViewport);
}
}
/**
* When we have multiple internal displays make sure we always return the default display when
* querying by type.
*/
TEST_F(InputReaderPolicyTest, Viewports_ByTypeReturnsDefaultForInternal) {
const std::string uniqueId1 = "uniqueId1";
const std::string uniqueId2 = "uniqueId2";
constexpr int32_t nonDefaultDisplayId = 2;
static_assert(nonDefaultDisplayId != ADISPLAY_ID_DEFAULT,
"Test display ID should not be ADISPLAY_ID_DEFAULT");
// Add the default display first and ensure it gets returned.
mFakePolicy->clearViewports();
mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1, NO_PORT,
ViewportType::INTERNAL);
mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2, NO_PORT,
ViewportType::INTERNAL);
std::optional<DisplayViewport> viewport =
mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
ASSERT_TRUE(viewport);
ASSERT_EQ(ADISPLAY_ID_DEFAULT, viewport->displayId);
ASSERT_EQ(ViewportType::INTERNAL, viewport->type);
// Add the default display second to make sure order doesn't matter.
mFakePolicy->clearViewports();
mFakePolicy->addDisplayViewport(nonDefaultDisplayId, DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2, NO_PORT,
ViewportType::INTERNAL);
mFakePolicy->addDisplayViewport(ADISPLAY_ID_DEFAULT, DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1, NO_PORT,
ViewportType::INTERNAL);
viewport = mFakePolicy->getDisplayViewportByType(ViewportType::INTERNAL);
ASSERT_TRUE(viewport);
ASSERT_EQ(ADISPLAY_ID_DEFAULT, viewport->displayId);
ASSERT_EQ(ViewportType::INTERNAL, viewport->type);
}
/**
* Check getDisplayViewportByPort
*/
TEST_F(InputReaderPolicyTest, Viewports_GetByPort) {
constexpr ViewportType type = ViewportType::EXTERNAL;
const std::string uniqueId1 = "uniqueId1";
const std::string uniqueId2 = "uniqueId2";
constexpr int32_t displayId1 = 1;
constexpr int32_t displayId2 = 2;
const uint8_t hdmi1 = 0;
const uint8_t hdmi2 = 1;
const uint8_t hdmi3 = 2;
mFakePolicy->clearViewports();
// Add a viewport that's associated with some display port that's not of interest.
mFakePolicy->addDisplayViewport(displayId1, DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId1, hdmi3,
type);
// Add another viewport, connected to HDMI1 port
mFakePolicy->addDisplayViewport(displayId2, DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_ORIENTATION_0, true /*isActive*/, uniqueId2, hdmi1,
type);
// Check that correct display viewport was returned by comparing the display ports.
std::optional<DisplayViewport> hdmi1Viewport = mFakePolicy->getDisplayViewportByPort(hdmi1);
ASSERT_TRUE(hdmi1Viewport);
ASSERT_EQ(displayId2, hdmi1Viewport->displayId);
ASSERT_EQ(uniqueId2, hdmi1Viewport->uniqueId);
// Check that we can still get the same viewport using the uniqueId
hdmi1Viewport = mFakePolicy->getDisplayViewportByUniqueId(uniqueId2);
ASSERT_TRUE(hdmi1Viewport);
ASSERT_EQ(displayId2, hdmi1Viewport->displayId);
ASSERT_EQ(uniqueId2, hdmi1Viewport->uniqueId);
ASSERT_EQ(type, hdmi1Viewport->type);
// Check that we cannot find a port with "HDMI2", because we never added one
std::optional<DisplayViewport> hdmi2Viewport = mFakePolicy->getDisplayViewportByPort(hdmi2);
ASSERT_FALSE(hdmi2Viewport);
}
// --- InputReaderTest ---
class InputReaderTest : public testing::Test {
protected:
std::unique_ptr<TestInputListener> mFakeListener;
sp<FakeInputReaderPolicy> mFakePolicy;
std::shared_ptr<FakeEventHub> mFakeEventHub;
std::unique_ptr<InstrumentedInputReader> mReader;
void SetUp() override {
mFakeEventHub = std::make_unique<FakeEventHub>();
mFakePolicy = new FakeInputReaderPolicy();
mFakeListener = std::make_unique<TestInputListener>();
mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy,
*mFakeListener);
}
void TearDown() override {
mFakeListener.reset();
mFakePolicy.clear();
}
void addDevice(int32_t eventHubId, const std::string& name,
ftl::Flags<InputDeviceClass> classes, const PropertyMap* configuration) {
mFakeEventHub->addDevice(eventHubId, name, classes);
if (configuration) {
mFakeEventHub->addConfigurationMap(eventHubId, configuration);
}
mFakeEventHub->finishDeviceScan();
mReader->loopOnce();
mReader->loopOnce();
ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
ASSERT_NO_FATAL_FAILURE(mFakeEventHub->assertQueueIsEmpty());
}
void disableDevice(int32_t deviceId) {
mFakePolicy->addDisabledDevice(deviceId);
mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_ENABLED_STATE);
}
void enableDevice(int32_t deviceId) {
mFakePolicy->removeDisabledDevice(deviceId);
mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_ENABLED_STATE);
}
FakeInputMapper& addDeviceWithFakeInputMapper(int32_t deviceId, int32_t eventHubId,
const std::string& name,
ftl::Flags<InputDeviceClass> classes,
uint32_t sources,
const PropertyMap* configuration) {
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, name);
FakeInputMapper& mapper = device->addMapper<FakeInputMapper>(eventHubId, sources);
mReader->pushNextDevice(device);
addDevice(eventHubId, name, classes, configuration);
return mapper;
}
};
TEST_F(InputReaderTest, PolicyGetInputDevices) {
ASSERT_NO_FATAL_FAILURE(addDevice(1, "keyboard", InputDeviceClass::KEYBOARD, nullptr));
ASSERT_NO_FATAL_FAILURE(addDevice(2, "ignored", ftl::Flags<InputDeviceClass>(0),
nullptr)); // no classes so device will be ignored
// Should also have received a notification describing the new input devices.
const std::vector<InputDeviceInfo>& inputDevices = mFakePolicy->getInputDevices();
ASSERT_EQ(1U, inputDevices.size());
ASSERT_EQ(END_RESERVED_ID + 1, inputDevices[0].getId());
ASSERT_STREQ("keyboard", inputDevices[0].getIdentifier().name.c_str());
ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, inputDevices[0].getKeyboardType());
ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, inputDevices[0].getSources());
ASSERT_EQ(0U, inputDevices[0].getMotionRanges().size());
}
TEST_F(InputReaderTest, GetMergedInputDevices) {
constexpr int32_t deviceId = END_RESERVED_ID + 1000;
constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1};
// Add two subdevices to device
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
// Must add at least one mapper or the device will be ignored!
device->addMapper<FakeInputMapper>(eventHubIds[0], AINPUT_SOURCE_KEYBOARD);
device->addMapper<FakeInputMapper>(eventHubIds[1], AINPUT_SOURCE_KEYBOARD);
// Push same device instance for next device to be added, so they'll have same identifier.
mReader->pushNextDevice(device);
mReader->pushNextDevice(device);
ASSERT_NO_FATAL_FAILURE(
addDevice(eventHubIds[0], "fake1", InputDeviceClass::KEYBOARD, nullptr));
ASSERT_NO_FATAL_FAILURE(
addDevice(eventHubIds[1], "fake2", InputDeviceClass::KEYBOARD, nullptr));
// Two devices will be merged to one input device as they have same identifier
ASSERT_EQ(1U, mFakePolicy->getInputDevices().size());
}
TEST_F(InputReaderTest, GetMergedInputDevicesEnabled) {
constexpr int32_t deviceId = END_RESERVED_ID + 1000;
constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1};
// Add two subdevices to device
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
// Must add at least one mapper or the device will be ignored!
device->addMapper<FakeInputMapper>(eventHubIds[0], AINPUT_SOURCE_KEYBOARD);
device->addMapper<FakeInputMapper>(eventHubIds[1], AINPUT_SOURCE_KEYBOARD);
// Push same device instance for next device to be added, so they'll have same identifier.
mReader->pushNextDevice(device);
mReader->pushNextDevice(device);
// Sensor device is initially disabled
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "fake1",
InputDeviceClass::KEYBOARD | InputDeviceClass::SENSOR,
nullptr));
// Device is disabled because the only sub device is a sensor device and disabled initially.
ASSERT_FALSE(mFakeEventHub->isDeviceEnabled(eventHubIds[0]));
ASSERT_FALSE(device->isEnabled());
ASSERT_NO_FATAL_FAILURE(
addDevice(eventHubIds[1], "fake2", InputDeviceClass::KEYBOARD, nullptr));
// The merged device is enabled if any sub device is enabled
ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(eventHubIds[1]));
ASSERT_TRUE(device->isEnabled());
}
TEST_F(InputReaderTest, WhenEnabledChanges_SendsDeviceResetNotification) {
constexpr int32_t deviceId = END_RESERVED_ID + 1000;
constexpr ftl::Flags<InputDeviceClass> deviceClass(InputDeviceClass::KEYBOARD);
constexpr int32_t eventHubId = 1;
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
// Must add at least one mapper or the device will be ignored!
device->addMapper<FakeInputMapper>(eventHubId, AINPUT_SOURCE_KEYBOARD);
mReader->pushNextDevice(device);
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasCalled(nullptr));
NotifyDeviceResetArgs resetArgs;
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
ASSERT_EQ(deviceId, resetArgs.deviceId);
ASSERT_EQ(device->isEnabled(), true);
disableDevice(deviceId);
mReader->loopOnce();
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
ASSERT_EQ(deviceId, resetArgs.deviceId);
ASSERT_EQ(device->isEnabled(), false);
disableDevice(deviceId);
mReader->loopOnce();
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasNotCalled());
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasNotCalled());
ASSERT_EQ(device->isEnabled(), false);
enableDevice(deviceId);
mReader->loopOnce();
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
ASSERT_EQ(deviceId, resetArgs.deviceId);
ASSERT_EQ(device->isEnabled(), true);
}
TEST_F(InputReaderTest, GetKeyCodeState_ForwardsRequestsToMappers) {
constexpr int32_t deviceId = END_RESERVED_ID + 1000;
constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
constexpr int32_t eventHubId = 1;
FakeInputMapper& mapper =
addDeviceWithFakeInputMapper(deviceId, eventHubId, "fake", deviceClass,
AINPUT_SOURCE_KEYBOARD, nullptr);
mapper.setKeyCodeState(AKEYCODE_A, AKEY_STATE_DOWN);
ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(0,
AINPUT_SOURCE_ANY, AKEYCODE_A))
<< "Should return unknown when the device id is >= 0 but unknown.";
ASSERT_EQ(AKEY_STATE_UNKNOWN,
mReader->getKeyCodeState(deviceId, AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
<< "Should return unknown when the device id is valid but the sources are not "
"supported by the device.";
ASSERT_EQ(AKEY_STATE_DOWN,
mReader->getKeyCodeState(deviceId, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL,
AKEYCODE_A))
<< "Should return value provided by mapper when device id is valid and the device "
"supports some of the sources.";
ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getKeyCodeState(-1,
AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
<< "Should return unknown when the device id is < 0 but the sources are not supported by any device.";
ASSERT_EQ(AKEY_STATE_DOWN, mReader->getKeyCodeState(-1,
AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, AKEYCODE_A))
<< "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
}
TEST_F(InputReaderTest, GetKeyCodeForKeyLocation_ForwardsRequestsToMappers) {
constexpr int32_t deviceId = END_RESERVED_ID + 1000;
constexpr int32_t eventHubId = 1;
FakeInputMapper& mapper = addDeviceWithFakeInputMapper(deviceId, eventHubId, "keyboard",
InputDeviceClass::KEYBOARD,
AINPUT_SOURCE_KEYBOARD, nullptr);
mapper.addKeyCodeMapping(AKEYCODE_Y, AKEYCODE_Z);
ASSERT_EQ(AKEYCODE_UNKNOWN, mReader->getKeyCodeForKeyLocation(0, AKEYCODE_Y))
<< "Should return unknown when the device with the specified id is not found.";
ASSERT_EQ(AKEYCODE_Z, mReader->getKeyCodeForKeyLocation(deviceId, AKEYCODE_Y))
<< "Should return correct mapping when device id is valid and mapping exists.";
ASSERT_EQ(AKEYCODE_A, mReader->getKeyCodeForKeyLocation(deviceId, AKEYCODE_A))
<< "Should return the location key code when device id is valid and there's no "
"mapping.";
}
TEST_F(InputReaderTest, GetKeyCodeForKeyLocation_NoKeyboardMapper) {
constexpr int32_t deviceId = END_RESERVED_ID + 1000;
constexpr int32_t eventHubId = 1;
FakeInputMapper& mapper = addDeviceWithFakeInputMapper(deviceId, eventHubId, "joystick",
InputDeviceClass::JOYSTICK,
AINPUT_SOURCE_GAMEPAD, nullptr);
mapper.addKeyCodeMapping(AKEYCODE_Y, AKEYCODE_Z);
ASSERT_EQ(AKEYCODE_UNKNOWN, mReader->getKeyCodeForKeyLocation(deviceId, AKEYCODE_Y))
<< "Should return unknown when the device id is valid but there is no keyboard mapper";
}
TEST_F(InputReaderTest, GetScanCodeState_ForwardsRequestsToMappers) {
constexpr int32_t deviceId = END_RESERVED_ID + 1000;
constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
constexpr int32_t eventHubId = 1;
FakeInputMapper& mapper =
addDeviceWithFakeInputMapper(deviceId, eventHubId, "fake", deviceClass,
AINPUT_SOURCE_KEYBOARD, nullptr);
mapper.setScanCodeState(KEY_A, AKEY_STATE_DOWN);
ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(0,
AINPUT_SOURCE_ANY, KEY_A))
<< "Should return unknown when the device id is >= 0 but unknown.";
ASSERT_EQ(AKEY_STATE_UNKNOWN,
mReader->getScanCodeState(deviceId, AINPUT_SOURCE_TRACKBALL, KEY_A))
<< "Should return unknown when the device id is valid but the sources are not "
"supported by the device.";
ASSERT_EQ(AKEY_STATE_DOWN,
mReader->getScanCodeState(deviceId, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL,
KEY_A))
<< "Should return value provided by mapper when device id is valid and the device "
"supports some of the sources.";
ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getScanCodeState(-1,
AINPUT_SOURCE_TRACKBALL, KEY_A))
<< "Should return unknown when the device id is < 0 but the sources are not supported by any device.";
ASSERT_EQ(AKEY_STATE_DOWN, mReader->getScanCodeState(-1,
AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, KEY_A))
<< "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
}
TEST_F(InputReaderTest, GetSwitchState_ForwardsRequestsToMappers) {
constexpr int32_t deviceId = END_RESERVED_ID + 1000;
constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
constexpr int32_t eventHubId = 1;
FakeInputMapper& mapper =
addDeviceWithFakeInputMapper(deviceId, eventHubId, "fake", deviceClass,
AINPUT_SOURCE_KEYBOARD, nullptr);
mapper.setSwitchState(SW_LID, AKEY_STATE_DOWN);
ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(0,
AINPUT_SOURCE_ANY, SW_LID))
<< "Should return unknown when the device id is >= 0 but unknown.";
ASSERT_EQ(AKEY_STATE_UNKNOWN,
mReader->getSwitchState(deviceId, AINPUT_SOURCE_TRACKBALL, SW_LID))
<< "Should return unknown when the device id is valid but the sources are not "
"supported by the device.";
ASSERT_EQ(AKEY_STATE_DOWN,
mReader->getSwitchState(deviceId, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL,
SW_LID))
<< "Should return value provided by mapper when device id is valid and the device "
"supports some of the sources.";
ASSERT_EQ(AKEY_STATE_UNKNOWN, mReader->getSwitchState(-1,
AINPUT_SOURCE_TRACKBALL, SW_LID))
<< "Should return unknown when the device id is < 0 but the sources are not supported by any device.";
ASSERT_EQ(AKEY_STATE_DOWN, mReader->getSwitchState(-1,
AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, SW_LID))
<< "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
}
TEST_F(InputReaderTest, MarkSupportedKeyCodes_ForwardsRequestsToMappers) {
constexpr int32_t deviceId = END_RESERVED_ID + 1000;
constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
constexpr int32_t eventHubId = 1;
FakeInputMapper& mapper =
addDeviceWithFakeInputMapper(deviceId, eventHubId, "fake", deviceClass,
AINPUT_SOURCE_KEYBOARD, nullptr);
mapper.addSupportedKeyCode(AKEYCODE_A);
mapper.addSupportedKeyCode(AKEYCODE_B);
const int32_t keyCodes[4] = { AKEYCODE_A, AKEYCODE_B, AKEYCODE_1, AKEYCODE_2 };
uint8_t flags[4] = { 0, 0, 0, 1 };
ASSERT_FALSE(mReader->hasKeys(0, AINPUT_SOURCE_ANY, 4, keyCodes, flags))
<< "Should return false when device id is >= 0 but unknown.";
ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]);
flags[3] = 1;
ASSERT_FALSE(mReader->hasKeys(deviceId, AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
<< "Should return false when device id is valid but the sources are not supported by "
"the device.";
ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]);
flags[3] = 1;
ASSERT_TRUE(mReader->hasKeys(deviceId, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, 4,
keyCodes, flags))
<< "Should return value provided by mapper when device id is valid and the device "
"supports some of the sources.";
ASSERT_TRUE(flags[0] && flags[1] && !flags[2] && !flags[3]);
flags[3] = 1;
ASSERT_FALSE(mReader->hasKeys(-1, AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
<< "Should return false when the device id is < 0 but the sources are not supported by any device.";
ASSERT_TRUE(!flags[0] && !flags[1] && !flags[2] && !flags[3]);
flags[3] = 1;
ASSERT_TRUE(mReader->hasKeys(-1, AINPUT_SOURCE_KEYBOARD | AINPUT_SOURCE_TRACKBALL, 4, keyCodes, flags))
<< "Should return value provided by mapper when device id is < 0 and one of the devices supports some of the sources.";
ASSERT_TRUE(flags[0] && flags[1] && !flags[2] && !flags[3]);
}
TEST_F(InputReaderTest, LoopOnce_WhenDeviceScanFinished_SendsConfigurationChanged) {
constexpr int32_t eventHubId = 1;
addDevice(eventHubId, "ignored", InputDeviceClass::KEYBOARD, nullptr);
NotifyConfigurationChangedArgs args;
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasCalled(&args));
ASSERT_EQ(ARBITRARY_TIME, args.eventTime);
}
TEST_F(InputReaderTest, LoopOnce_ForwardsRawEventsToMappers) {
constexpr int32_t deviceId = END_RESERVED_ID + 1000;
constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
constexpr nsecs_t when = 0;
constexpr int32_t eventHubId = 1;
constexpr nsecs_t readTime = 2;
FakeInputMapper& mapper =
addDeviceWithFakeInputMapper(deviceId, eventHubId, "fake", deviceClass,
AINPUT_SOURCE_KEYBOARD, nullptr);
mFakeEventHub->enqueueEvent(when, readTime, eventHubId, EV_KEY, KEY_A, 1);
mReader->loopOnce();
ASSERT_NO_FATAL_FAILURE(mFakeEventHub->assertQueueIsEmpty());
RawEvent event;
ASSERT_NO_FATAL_FAILURE(mapper.assertProcessWasCalled(&event));
ASSERT_EQ(when, event.when);
ASSERT_EQ(readTime, event.readTime);
ASSERT_EQ(eventHubId, event.deviceId);
ASSERT_EQ(EV_KEY, event.type);
ASSERT_EQ(KEY_A, event.code);
ASSERT_EQ(1, event.value);
}
TEST_F(InputReaderTest, DeviceReset_RandomId) {
constexpr int32_t deviceId = END_RESERVED_ID + 1000;
constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
constexpr int32_t eventHubId = 1;
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
// Must add at least one mapper or the device will be ignored!
device->addMapper<FakeInputMapper>(eventHubId, AINPUT_SOURCE_KEYBOARD);
mReader->pushNextDevice(device);
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
NotifyDeviceResetArgs resetArgs;
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
int32_t prevId = resetArgs.id;
disableDevice(deviceId);
mReader->loopOnce();
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
ASSERT_NE(prevId, resetArgs.id);
prevId = resetArgs.id;
enableDevice(deviceId);
mReader->loopOnce();
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
ASSERT_NE(prevId, resetArgs.id);
prevId = resetArgs.id;
disableDevice(deviceId);
mReader->loopOnce();
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
ASSERT_NE(prevId, resetArgs.id);
prevId = resetArgs.id;
}
TEST_F(InputReaderTest, DeviceReset_GenerateIdWithInputReaderSource) {
constexpr int32_t deviceId = 1;
constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
constexpr int32_t eventHubId = 1;
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
// Must add at least one mapper or the device will be ignored!
device->addMapper<FakeInputMapper>(eventHubId, AINPUT_SOURCE_KEYBOARD);
mReader->pushNextDevice(device);
ASSERT_NO_FATAL_FAILURE(addDevice(deviceId, "fake", deviceClass, nullptr));
NotifyDeviceResetArgs resetArgs;
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
ASSERT_EQ(IdGenerator::Source::INPUT_READER, IdGenerator::getSource(resetArgs.id));
}
TEST_F(InputReaderTest, Device_CanDispatchToDisplay) {
constexpr int32_t deviceId = END_RESERVED_ID + 1000;
constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
constexpr int32_t eventHubId = 1;
const char* DEVICE_LOCATION = "USB1";
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
FakeInputMapper& mapper =
device->addMapper<FakeInputMapper>(eventHubId, AINPUT_SOURCE_TOUCHSCREEN);
mReader->pushNextDevice(device);
const uint8_t hdmi1 = 1;
// Associated touch screen with second display.
mFakePolicy->addInputPortAssociation(DEVICE_LOCATION, hdmi1);
// Add default and second display.
mFakePolicy->clearViewports();
mFakePolicy->addDisplayViewport(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_ORIENTATION_0, true /*isActive*/, "local:0", NO_PORT,
ViewportType::INTERNAL);
mFakePolicy->addDisplayViewport(SECONDARY_DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_ORIENTATION_0, true /*isActive*/, "local:1", hdmi1,
ViewportType::EXTERNAL);
mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
mReader->loopOnce();
// Add the device, and make sure all of the callbacks are triggered.
// The device is added after the input port associations are processed since
// we do not yet support dynamic device-to-display associations.
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasCalled());
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled());
ASSERT_NO_FATAL_FAILURE(mapper.assertConfigureWasCalled());
// Device should only dispatch to the specified display.
ASSERT_EQ(deviceId, device->getId());
ASSERT_FALSE(mReader->canDispatchToDisplay(deviceId, DISPLAY_ID));
ASSERT_TRUE(mReader->canDispatchToDisplay(deviceId, SECONDARY_DISPLAY_ID));
// Can't dispatch event from a disabled device.
disableDevice(deviceId);
mReader->loopOnce();
ASSERT_FALSE(mReader->canDispatchToDisplay(deviceId, SECONDARY_DISPLAY_ID));
}
TEST_F(InputReaderTest, WhenEnabledChanges_AllSubdevicesAreUpdated) {
constexpr int32_t deviceId = END_RESERVED_ID + 1000;
constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1};
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
// Must add at least one mapper or the device will be ignored!
device->addMapper<FakeInputMapper>(eventHubIds[0], AINPUT_SOURCE_KEYBOARD);
device->addMapper<FakeInputMapper>(eventHubIds[1], AINPUT_SOURCE_KEYBOARD);
mReader->pushNextDevice(device);
mReader->pushNextDevice(device);
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "fake1", deviceClass, nullptr));
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[1], "fake2", deviceClass, nullptr));
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyConfigurationChangedWasCalled(nullptr));
NotifyDeviceResetArgs resetArgs;
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
ASSERT_EQ(deviceId, resetArgs.deviceId);
ASSERT_TRUE(device->isEnabled());
ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(eventHubIds[0]));
ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(eventHubIds[1]));
disableDevice(deviceId);
mReader->loopOnce();
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
ASSERT_EQ(deviceId, resetArgs.deviceId);
ASSERT_FALSE(device->isEnabled());
ASSERT_FALSE(mFakeEventHub->isDeviceEnabled(eventHubIds[0]));
ASSERT_FALSE(mFakeEventHub->isDeviceEnabled(eventHubIds[1]));
enableDevice(deviceId);
mReader->loopOnce();
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
ASSERT_EQ(deviceId, resetArgs.deviceId);
ASSERT_TRUE(device->isEnabled());
ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(eventHubIds[0]));
ASSERT_TRUE(mFakeEventHub->isDeviceEnabled(eventHubIds[1]));
}
TEST_F(InputReaderTest, GetKeyCodeState_ForwardsRequestsToSubdeviceMappers) {
constexpr int32_t deviceId = END_RESERVED_ID + 1000;
constexpr ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD;
constexpr int32_t eventHubIds[2] = {END_RESERVED_ID, END_RESERVED_ID + 1};
// Add two subdevices to device
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake");
FakeInputMapper& mapperDevice1 =
device->addMapper<FakeInputMapper>(eventHubIds[0], AINPUT_SOURCE_KEYBOARD);
FakeInputMapper& mapperDevice2 =
device->addMapper<FakeInputMapper>(eventHubIds[1], AINPUT_SOURCE_KEYBOARD);
mReader->pushNextDevice(device);
mReader->pushNextDevice(device);
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[0], "fake1", deviceClass, nullptr));
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubIds[1], "fake2", deviceClass, nullptr));
mapperDevice1.setKeyCodeState(AKEYCODE_A, AKEY_STATE_DOWN);
mapperDevice2.setKeyCodeState(AKEYCODE_B, AKEY_STATE_DOWN);
ASSERT_EQ(AKEY_STATE_DOWN,
mReader->getKeyCodeState(deviceId, AINPUT_SOURCE_KEYBOARD, AKEYCODE_A));
ASSERT_EQ(AKEY_STATE_DOWN,
mReader->getKeyCodeState(deviceId, AINPUT_SOURCE_KEYBOARD, AKEYCODE_B));
ASSERT_EQ(AKEY_STATE_UNKNOWN,
mReader->getKeyCodeState(deviceId, AINPUT_SOURCE_KEYBOARD, AKEYCODE_C));
}
TEST_F(InputReaderTest, ChangingPointerCaptureNotifiesInputListener) {
NotifyPointerCaptureChangedArgs args;
auto request = mFakePolicy->setPointerCapture(true);
mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
mReader->loopOnce();
mFakeListener->assertNotifyCaptureWasCalled(&args);
ASSERT_TRUE(args.request.enable) << "Pointer Capture should be enabled.";
ASSERT_EQ(args.request, request) << "Pointer Capture sequence number should match.";
mFakePolicy->setPointerCapture(false);
mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
mReader->loopOnce();
mFakeListener->assertNotifyCaptureWasCalled(&args);
ASSERT_FALSE(args.request.enable) << "Pointer Capture should be disabled.";
// Verify that the Pointer Capture state is not updated when the configuration value
// does not change.
mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_POINTER_CAPTURE);
mReader->loopOnce();
mFakeListener->assertNotifyCaptureWasNotCalled();
}
class FakeVibratorInputMapper : public FakeInputMapper {
public:
FakeVibratorInputMapper(InputDeviceContext& deviceContext, uint32_t sources)
: FakeInputMapper(deviceContext, sources) {}
std::vector<int32_t> getVibratorIds() override { return getDeviceContext().getVibratorIds(); }
};
TEST_F(InputReaderTest, VibratorGetVibratorIds) {
constexpr int32_t deviceId = END_RESERVED_ID + 1000;
ftl::Flags<InputDeviceClass> deviceClass =
InputDeviceClass::KEYBOARD | InputDeviceClass::VIBRATOR;
constexpr int32_t eventHubId = 1;
const char* DEVICE_LOCATION = "BLUETOOTH";
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
FakeVibratorInputMapper& mapper =
device->addMapper<FakeVibratorInputMapper>(eventHubId, AINPUT_SOURCE_KEYBOARD);
mReader->pushNextDevice(device);
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
ASSERT_NO_FATAL_FAILURE(mapper.assertConfigureWasCalled());
ASSERT_EQ(mapper.getVibratorIds().size(), 2U);
ASSERT_EQ(mReader->getVibratorIds(deviceId).size(), 2U);
}
// --- FakePeripheralController ---
class FakePeripheralController : public PeripheralControllerInterface {
public:
FakePeripheralController(InputDeviceContext& deviceContext) : mDeviceContext(deviceContext) {}
~FakePeripheralController() override {}
int32_t getEventHubId() const { return getDeviceContext().getEventHubId(); }
void populateDeviceInfo(InputDeviceInfo* deviceInfo) override {}
void dump(std::string& dump) override {}
std::optional<int32_t> getBatteryCapacity(int32_t batteryId) override {
return getDeviceContext().getBatteryCapacity(batteryId);
}
std::optional<int32_t> getBatteryStatus(int32_t batteryId) override {
return getDeviceContext().getBatteryStatus(batteryId);
}
bool setLightColor(int32_t lightId, int32_t color) override {
getDeviceContext().setLightBrightness(lightId, color >> 24);
return true;
}
std::optional<int32_t> getLightColor(int32_t lightId) override {
std::optional<int32_t> result = getDeviceContext().getLightBrightness(lightId);
if (!result.has_value()) {
return std::nullopt;
}
return result.value() << 24;
}
bool setLightPlayerId(int32_t lightId, int32_t playerId) override { return true; }
std::optional<int32_t> getLightPlayerId(int32_t lightId) override { return std::nullopt; }
private:
InputDeviceContext& mDeviceContext;
inline int32_t getDeviceId() { return mDeviceContext.getId(); }
inline InputDeviceContext& getDeviceContext() { return mDeviceContext; }
inline InputDeviceContext& getDeviceContext() const { return mDeviceContext; }
};
TEST_F(InputReaderTest, BatteryGetCapacity) {
constexpr int32_t deviceId = END_RESERVED_ID + 1000;
ftl::Flags<InputDeviceClass> deviceClass =
InputDeviceClass::KEYBOARD | InputDeviceClass::BATTERY;
constexpr int32_t eventHubId = 1;
const char* DEVICE_LOCATION = "BLUETOOTH";
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
FakePeripheralController& controller =
device->addController<FakePeripheralController>(eventHubId);
mReader->pushNextDevice(device);
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
ASSERT_EQ(controller.getBatteryCapacity(DEFAULT_BATTERY), BATTERY_CAPACITY);
ASSERT_EQ(mReader->getBatteryCapacity(deviceId), BATTERY_CAPACITY);
}
TEST_F(InputReaderTest, BatteryGetStatus) {
constexpr int32_t deviceId = END_RESERVED_ID + 1000;
ftl::Flags<InputDeviceClass> deviceClass =
InputDeviceClass::KEYBOARD | InputDeviceClass::BATTERY;
constexpr int32_t eventHubId = 1;
const char* DEVICE_LOCATION = "BLUETOOTH";
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
FakePeripheralController& controller =
device->addController<FakePeripheralController>(eventHubId);
mReader->pushNextDevice(device);
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
ASSERT_EQ(controller.getBatteryStatus(DEFAULT_BATTERY), BATTERY_STATUS);
ASSERT_EQ(mReader->getBatteryStatus(deviceId), BATTERY_STATUS);
}
TEST_F(InputReaderTest, LightGetColor) {
constexpr int32_t deviceId = END_RESERVED_ID + 1000;
ftl::Flags<InputDeviceClass> deviceClass = InputDeviceClass::KEYBOARD | InputDeviceClass::LIGHT;
constexpr int32_t eventHubId = 1;
const char* DEVICE_LOCATION = "BLUETOOTH";
std::shared_ptr<InputDevice> device = mReader->newDevice(deviceId, "fake", DEVICE_LOCATION);
FakePeripheralController& controller =
device->addController<FakePeripheralController>(eventHubId);
mReader->pushNextDevice(device);
RawLightInfo info = {.id = 1,
.name = "Mono",
.maxBrightness = 255,
.flags = InputLightClass::BRIGHTNESS,
.path = ""};
mFakeEventHub->addRawLightInfo(1 /* rawId */, std::move(info));
mFakeEventHub->fakeLightBrightness(1 /* rawId */, 0x55);
ASSERT_NO_FATAL_FAILURE(addDevice(eventHubId, "fake", deviceClass, nullptr));
ASSERT_TRUE(controller.setLightColor(1 /* lightId */, LIGHT_BRIGHTNESS));
ASSERT_EQ(controller.getLightColor(1 /* lightId */), LIGHT_BRIGHTNESS);
ASSERT_TRUE(mReader->setLightColor(deviceId, 1 /* lightId */, LIGHT_BRIGHTNESS));
ASSERT_EQ(mReader->getLightColor(deviceId, 1 /* lightId */), LIGHT_BRIGHTNESS);
}
// --- InputReaderIntegrationTest ---
// These tests create and interact with the InputReader only through its interface.
// The InputReader is started during SetUp(), which starts its processing in its own
// thread. The tests use linux uinput to emulate input devices.
// NOTE: Interacting with the physical device while these tests are running may cause
// the tests to fail.
class InputReaderIntegrationTest : public testing::Test {
protected:
std::unique_ptr<TestInputListener> mTestListener;
sp<FakeInputReaderPolicy> mFakePolicy;
std::unique_ptr<InputReaderInterface> mReader;
std::shared_ptr<FakePointerController> mFakePointerController;
void SetUp() override {
mFakePolicy = new FakeInputReaderPolicy();
mFakePointerController = std::make_shared<FakePointerController>();
mFakePolicy->setPointerController(mFakePointerController);
mTestListener = std::make_unique<TestInputListener>(2000ms /*eventHappenedTimeout*/,
30ms /*eventDidNotHappenTimeout*/);
mReader = std::make_unique<InputReader>(std::make_shared<EventHub>(), mFakePolicy,
*mTestListener);
ASSERT_EQ(mReader->start(), OK);
// Since this test is run on a real device, all the input devices connected
// to the test device will show up in mReader. We wait for those input devices to
// show up before beginning the tests.
ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
}
void TearDown() override {
ASSERT_EQ(mReader->stop(), OK);
mReader.reset();
mTestListener.reset();
mFakePolicy.clear();
}
};
TEST_F(InputReaderIntegrationTest, TestInvalidDevice) {
// An invalid input device that is only used for this test.
class InvalidUinputDevice : public UinputDevice {
public:
InvalidUinputDevice() : UinputDevice("Invalid Device") {}
private:
void configureDevice(int fd, uinput_user_dev* device) override {}
};
const size_t numDevices = mFakePolicy->getInputDevices().size();
// UinputDevice does not set any event or key bits, so InputReader should not
// consider it as a valid device.
std::unique_ptr<UinputDevice> invalidDevice = createUinputDevice<InvalidUinputDevice>();
ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesNotChanged());
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasNotCalled());
ASSERT_EQ(numDevices, mFakePolicy->getInputDevices().size());
invalidDevice.reset();
ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesNotChanged());
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasNotCalled());
ASSERT_EQ(numDevices, mFakePolicy->getInputDevices().size());
}
TEST_F(InputReaderIntegrationTest, AddNewDevice) {
const size_t initialNumDevices = mFakePolicy->getInputDevices().size();
std::unique_ptr<UinputHomeKey> keyboard = createUinputDevice<UinputHomeKey>();
ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
ASSERT_EQ(initialNumDevices + 1, mFakePolicy->getInputDevices().size());
// Find the test device by its name.
const std::vector<InputDeviceInfo> inputDevices = mFakePolicy->getInputDevices();
const auto& it =
std::find_if(inputDevices.begin(), inputDevices.end(),
[&keyboard](const InputDeviceInfo& info) {
return info.getIdentifier().name == keyboard->getName();
});
ASSERT_NE(it, inputDevices.end());
ASSERT_EQ(AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC, it->getKeyboardType());
ASSERT_EQ(AINPUT_SOURCE_KEYBOARD, it->getSources());
ASSERT_EQ(0U, it->getMotionRanges().size());
keyboard.reset();
ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
ASSERT_EQ(initialNumDevices, mFakePolicy->getInputDevices().size());
}
TEST_F(InputReaderIntegrationTest, SendsEventsToInputListener) {
std::unique_ptr<UinputHomeKey> keyboard = createUinputDevice<UinputHomeKey>();
ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
NotifyConfigurationChangedArgs configChangedArgs;
ASSERT_NO_FATAL_FAILURE(
mTestListener->assertNotifyConfigurationChangedWasCalled(&configChangedArgs));
int32_t prevId = configChangedArgs.id;
nsecs_t prevTimestamp = configChangedArgs.eventTime;
NotifyKeyArgs keyArgs;
keyboard->pressAndReleaseHomeKey();
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs));
ASSERT_EQ(AKEY_EVENT_ACTION_DOWN, keyArgs.action);
ASSERT_NE(prevId, keyArgs.id);
prevId = keyArgs.id;
ASSERT_LE(prevTimestamp, keyArgs.eventTime);
ASSERT_LE(keyArgs.eventTime, keyArgs.readTime);
prevTimestamp = keyArgs.eventTime;
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs));
ASSERT_EQ(AKEY_EVENT_ACTION_UP, keyArgs.action);
ASSERT_NE(prevId, keyArgs.id);
ASSERT_LE(prevTimestamp, keyArgs.eventTime);
ASSERT_LE(keyArgs.eventTime, keyArgs.readTime);
}
/**
* The Steam controller sends BTN_GEAR_DOWN and BTN_GEAR_UP for the two "paddle" buttons
* on the back. In this test, we make sure that BTN_GEAR_DOWN / BTN_WHEEL and BTN_GEAR_UP
* are passed to the listener.
*/
static_assert(BTN_GEAR_DOWN == BTN_WHEEL);
TEST_F(InputReaderIntegrationTest, SendsGearDownAndUpToInputListener) {
std::unique_ptr<UinputSteamController> controller = createUinputDevice<UinputSteamController>();
ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
NotifyKeyArgs keyArgs;
controller->pressAndReleaseKey(BTN_GEAR_DOWN);
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs)); // ACTION_DOWN
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs)); // ACTION_UP
ASSERT_EQ(BTN_GEAR_DOWN, keyArgs.scanCode);
controller->pressAndReleaseKey(BTN_GEAR_UP);
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs)); // ACTION_DOWN
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyKeyWasCalled(&keyArgs)); // ACTION_UP
ASSERT_EQ(BTN_GEAR_UP, keyArgs.scanCode);
}
// --- TouchProcessTest ---
class TouchIntegrationTest : public InputReaderIntegrationTest {
protected:
const std::string UNIQUE_ID = "local:0";
void SetUp() override {
InputReaderIntegrationTest::SetUp();
// At least add an internal display.
setDisplayInfoAndReconfigure(DISPLAY_ID, DISPLAY_WIDTH, DISPLAY_HEIGHT,
DISPLAY_ORIENTATION_0, UNIQUE_ID, NO_PORT,
ViewportType::INTERNAL);
mDevice = createUinputDevice<UinputTouchScreen>(Rect(0, 0, DISPLAY_WIDTH, DISPLAY_HEIGHT));
ASSERT_NO_FATAL_FAILURE(mFakePolicy->assertInputDevicesChanged());
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyConfigurationChangedWasCalled());
}
void setDisplayInfoAndReconfigure(int32_t displayId, int32_t width, int32_t height,
int32_t orientation, const std::string& uniqueId,
std::optional<uint8_t> physicalPort,
ViewportType viewportType) {
mFakePolicy->addDisplayViewport(displayId, width, height, orientation, true /*isActive*/,
uniqueId, physicalPort, viewportType);
mReader->requestRefreshConfiguration(InputReaderConfiguration::CHANGE_DISPLAY_INFO);
}
void assertReceivedMotion(int32_t action, const std::vector<Point>& points) {
NotifyMotionArgs args;
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
EXPECT_EQ(action, args.action);
ASSERT_EQ(points.size(), args.pointerCount);
for (size_t i = 0; i < args.pointerCount; i++) {
EXPECT_EQ(points[i].x, args.pointerCoords[i].getX());
EXPECT_EQ(points[i].y, args.pointerCoords[i].getY());
}
}
std::unique_ptr<UinputTouchScreen> mDevice;
};
TEST_F(TouchIntegrationTest, InputEvent_ProcessSingleTouch) {
NotifyMotionArgs args;
const Point centerPoint = mDevice->getCenterPoint();
// ACTION_DOWN
mDevice->sendTrackingId(FIRST_TRACKING_ID);
mDevice->sendDown(centerPoint);
mDevice->sendSync();
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
// ACTION_MOVE
mDevice->sendMove(centerPoint + Point(1, 1));
mDevice->sendSync();
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
// ACTION_UP
mDevice->sendUp();
mDevice->sendSync();
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
}
TEST_F(TouchIntegrationTest, InputEvent_ProcessMultiTouch) {
NotifyMotionArgs args;
const Point centerPoint = mDevice->getCenterPoint();
// ACTION_DOWN
mDevice->sendSlot(FIRST_SLOT);
mDevice->sendTrackingId(FIRST_TRACKING_ID);
mDevice->sendDown(centerPoint);
mDevice->sendSync();
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
// ACTION_POINTER_DOWN (Second slot)
const Point secondPoint = centerPoint + Point(100, 100);
mDevice->sendSlot(SECOND_SLOT);
mDevice->sendTrackingId(SECOND_TRACKING_ID);
mDevice->sendDown(secondPoint);
mDevice->sendSync();
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(ACTION_POINTER_1_DOWN, args.action);
// ACTION_MOVE (Second slot)
mDevice->sendMove(secondPoint + Point(1, 1));
mDevice->sendSync();
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
// ACTION_POINTER_UP (Second slot)
mDevice->sendPointerUp();
mDevice->sendSync();
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(ACTION_POINTER_1_UP, args.action);
// ACTION_UP
mDevice->sendSlot(FIRST_SLOT);
mDevice->sendUp();
mDevice->sendSync();
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
}
/**
* What happens when a pointer goes up while another pointer moves in the same frame? Are POINTER_UP
* events guaranteed to contain the same data as a preceding MOVE, or can they contain different
* data?
* In this test, we try to send a change in coordinates in Pointer 0 in the same frame as the
* liftoff of Pointer 1. We check that POINTER_UP event is generated first, and the MOVE event
* for Pointer 0 only is generated after.
* Suppose we are only interested in learning the movement of Pointer 0. If we only observe MOVE
* events, we will not miss any information.
* Even though the Pointer 1 up event contains updated Pointer 0 coordinates, there is another MOVE
* event generated afterwards that contains the newest movement of pointer 0.
* This is important for palm rejection. If there is a subsequent InputListener stage that detects
* palms, and wants to cancel Pointer 1, then it is safe to simply drop POINTER_1_UP event without
* losing information about non-palm pointers.
*/
TEST_F(TouchIntegrationTest, MultiTouch_PointerMoveAndSecondPointerUp) {
NotifyMotionArgs args;
const Point centerPoint = mDevice->getCenterPoint();
// ACTION_DOWN
mDevice->sendSlot(FIRST_SLOT);
mDevice->sendTrackingId(FIRST_TRACKING_ID);
mDevice->sendDown(centerPoint);
mDevice->sendSync();
assertReceivedMotion(AMOTION_EVENT_ACTION_DOWN, {centerPoint});
// ACTION_POINTER_DOWN (Second slot)
const Point secondPoint = centerPoint + Point(100, 100);
mDevice->sendSlot(SECOND_SLOT);
mDevice->sendTrackingId(SECOND_TRACKING_ID);
mDevice->sendDown(secondPoint);
mDevice->sendSync();
assertReceivedMotion(ACTION_POINTER_1_DOWN, {centerPoint, secondPoint});
// ACTION_MOVE (First slot)
mDevice->sendSlot(FIRST_SLOT);
mDevice->sendMove(centerPoint + Point(5, 5));
// ACTION_POINTER_UP (Second slot)
mDevice->sendSlot(SECOND_SLOT);
mDevice->sendPointerUp();
// Send a single sync for the above 2 pointer updates
mDevice->sendSync();
// First, we should get POINTER_UP for the second pointer
assertReceivedMotion(ACTION_POINTER_1_UP,
{/*first pointer */ centerPoint + Point(5, 5),
/*second pointer*/ secondPoint});
// Next, the MOVE event for the first pointer
assertReceivedMotion(AMOTION_EVENT_ACTION_MOVE, {centerPoint + Point(5, 5)});
}
/**
* Similar scenario as above. The difference is that when the second pointer goes up, it will first
* move, and then it will go up, all in the same frame.
* In this scenario, the movement of the second pointer just prior to liftoff is ignored, and never
* gets sent to the listener.
*/
TEST_F(TouchIntegrationTest, MultiTouch_PointerMoveAndSecondPointerMoveAndUp) {
NotifyMotionArgs args;
const Point centerPoint = mDevice->getCenterPoint();
// ACTION_DOWN
mDevice->sendSlot(FIRST_SLOT);
mDevice->sendTrackingId(FIRST_TRACKING_ID);
mDevice->sendDown(centerPoint);
mDevice->sendSync();
assertReceivedMotion(AMOTION_EVENT_ACTION_DOWN, {centerPoint});
// ACTION_POINTER_DOWN (Second slot)
const Point secondPoint = centerPoint + Point(100, 100);
mDevice->sendSlot(SECOND_SLOT);
mDevice->sendTrackingId(SECOND_TRACKING_ID);
mDevice->sendDown(secondPoint);
mDevice->sendSync();
assertReceivedMotion(ACTION_POINTER_1_DOWN, {centerPoint, secondPoint});
// ACTION_MOVE (First slot)
mDevice->sendSlot(FIRST_SLOT);
mDevice->sendMove(centerPoint + Point(5, 5));
// ACTION_POINTER_UP (Second slot)
mDevice->sendSlot(SECOND_SLOT);
mDevice->sendMove(secondPoint + Point(6, 6));
mDevice->sendPointerUp();
// Send a single sync for the above 2 pointer updates
mDevice->sendSync();
// First, we should get POINTER_UP for the second pointer
// The movement of the second pointer during the liftoff frame is ignored.
// The coordinates 'secondPoint + Point(6, 6)' are never sent to the listener.
assertReceivedMotion(ACTION_POINTER_1_UP,
{/*first pointer */ centerPoint + Point(5, 5),
/*second pointer*/ secondPoint});
// Next, the MOVE event for the first pointer
assertReceivedMotion(AMOTION_EVENT_ACTION_MOVE, {centerPoint + Point(5, 5)});
}
TEST_F(TouchIntegrationTest, InputEvent_ProcessPalm) {
NotifyMotionArgs args;
const Point centerPoint = mDevice->getCenterPoint();
// ACTION_DOWN
mDevice->sendSlot(FIRST_SLOT);
mDevice->sendTrackingId(FIRST_TRACKING_ID);
mDevice->sendDown(centerPoint);
mDevice->sendSync();
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_DOWN, args.action);
// ACTION_POINTER_DOWN (second slot)
const Point secondPoint = centerPoint + Point(100, 100);
mDevice->sendSlot(SECOND_SLOT);
mDevice->sendTrackingId(SECOND_TRACKING_ID);
mDevice->sendDown(secondPoint);
mDevice->sendSync();
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(ACTION_POINTER_1_DOWN, args.action);
// ACTION_MOVE (second slot)
mDevice->sendMove(secondPoint + Point(1, 1));
mDevice->sendSync();
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
// Send MT_TOOL_PALM (second slot), which indicates that the touch IC has determined this to be
// a palm event.
// Expect to receive the ACTION_POINTER_UP with cancel flag.
mDevice->sendToolType(MT_TOOL_PALM);
mDevice->sendSync();
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(ACTION_POINTER_1_UP, args.action);
ASSERT_EQ(AMOTION_EVENT_FLAG_CANCELED, args.flags);
// Send up to second slot, expect first slot send moving.
mDevice->sendPointerUp();
mDevice->sendSync();
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_MOVE, args.action);
// Send ACTION_UP (first slot)
mDevice->sendSlot(FIRST_SLOT);
mDevice->sendUp();
mDevice->sendSync();
ASSERT_NO_FATAL_FAILURE(mTestListener->assertNotifyMotionWasCalled(&args));
ASSERT_EQ(AMOTION_EVENT_ACTION_UP, args.action);
}
// --- InputDeviceTest ---
class InputDeviceTest : public testing::Test {
protected:
static const char* DEVICE_NAME;
static const char* DEVICE_LOCATION;
static const int32_t DEVICE_ID;
static const int32_t DEVICE_GENERATION;
static const int32_t DEVICE_CONTROLLER_NUMBER;
static const ftl::Flags<InputDeviceClass> DEVICE_CLASSES;
static const int32_t EVENTHUB_ID;
std::shared_ptr<FakeEventHub> mFakeEventHub;
sp<FakeInputReaderPolicy> mFakePolicy;
std::unique_ptr<TestInputListener> mFakeListener;
std::unique_ptr<InstrumentedInputReader> mReader;
std::shared_ptr<InputDevice> mDevice;
void SetUp() override {
mFakeEventHub = std::make_unique<FakeEventHub>();
mFakePolicy = new FakeInputReaderPolicy();
mFakeListener = std::make_unique<TestInputListener>();
mReader = std::make_unique<InstrumentedInputReader>(mFakeEventHub, mFakePolicy,
*mFakeListener);
InputDeviceIdentifier identifier;
identifier.name = DEVICE_NAME;
identifier.location = DEVICE_LOCATION;
mDevice = std::make_shared<InputDevice>(mReader->getContext(), DEVICE_ID, DEVICE_GENERATION,
identifier);
mReader->pushNextDevice(mDevice);
mFakeEventHub->addDevice(EVENTHUB_ID, DEVICE_NAME, ftl::Flags<InputDeviceClass>(0));
mReader->loopOnce();
}
void TearDown() override {
mFakeListener.reset();
mFakePolicy.clear();
}
};
const char* InputDeviceTest::DEVICE_NAME = "device";
const char* InputDeviceTest::DEVICE_LOCATION = "USB1";
const int32_t InputDeviceTest::DEVICE_ID = END_RESERVED_ID + 1000;
const int32_t InputDeviceTest::DEVICE_GENERATION = 2;
const int32_t InputDeviceTest::DEVICE_CONTROLLER_NUMBER = 0;
const ftl::Flags<InputDeviceClass> InputDeviceTest::DEVICE_CLASSES =
InputDeviceClass::KEYBOARD | InputDeviceClass::TOUCH | InputDeviceClass::JOYSTICK;
const int32_t InputDeviceTest::EVENTHUB_ID = 1;
TEST_F(InputDeviceTest, ImmutableProperties) {
ASSERT_EQ(DEVICE_ID, mDevice->getId());
ASSERT_STREQ(DEVICE_NAME, mDevice->getName().c_str());
ASSERT_EQ(ftl::Flags<InputDeviceClass>(0), mDevice->getClasses());
}
TEST_F(InputDeviceTest, WhenDeviceCreated_EnabledIsFalse) {
ASSERT_EQ(mDevice->isEnabled(), false);
}
TEST_F(InputDeviceTest, WhenNoMappersAreRegistered_DeviceIsIgnored) {
// Configuration.
InputReaderConfiguration config;
mDevice->configure(ARBITRARY_TIME, &config, 0);
// Reset.
mDevice->reset(ARBITRARY_TIME);
NotifyDeviceResetArgs resetArgs;
ASSERT_NO_FATAL_FAILURE(mFakeListener->assertNotifyDeviceResetWasCalled(&resetArgs));
ASSERT_EQ(ARBITRARY_TIME, resetArgs.eventTime);
ASSERT_EQ(DEVICE_ID, resetArgs.deviceId);
// Metadata.
ASSERT_TRUE(mDevice->isIgnored());
ASSERT_EQ(AINPUT_SOURCE_UNKNOWN, mDevice->getSources());
InputDeviceInfo info = mDevice->getDeviceInfo();
ASSERT_EQ(DEVICE_ID, info.getId());
ASSERT_STREQ(DEVICE_NAME, info.getIdentifier().name.c_str());
ASSERT_EQ