Add new hover move action and scroll wheel plumbing.
Added support for tracking the mouse position even when the mouse button
is not pressed. To avoid confusing existing applications, mouse movements
are reported using the new ACTION_HOVER_MOVE action when the mouse button
is not pressed.
Added some more plumbing for the scroll wheel axes. The values are
reported to Views but they are not yet handled by the framework.
Change-Id: I1706be850d25cf34e5adf880bbed5cc3265cf4b1
diff --git a/services/input/EventHub.cpp b/services/input/EventHub.cpp
index f79d106..b31381a 100644
--- a/services/input/EventHub.cpp
+++ b/services/input/EventHub.cpp
@@ -101,12 +101,14 @@
const InputDeviceIdentifier& identifier) :
next(NULL),
fd(fd), id(id), path(path), identifier(identifier),
- classes(0), keyBitmask(NULL), configuration(NULL), virtualKeyMap(NULL) {
+ classes(0), keyBitmask(NULL), relBitmask(NULL),
+ configuration(NULL), virtualKeyMap(NULL) {
}
EventHub::Device::~Device() {
close();
delete[] keyBitmask;
+ delete[] relBitmask;
delete configuration;
delete virtualKeyMap;
}
@@ -189,6 +191,18 @@
return OK;
}
+bool EventHub::hasRelativeAxis(int32_t deviceId, int axis) const {
+ if (axis >= 0 && axis <= REL_MAX) {
+ AutoMutex _l(mLock);
+
+ Device* device = getDeviceLocked(deviceId);
+ if (device && device->relBitmask) {
+ return test_bit(axis, device->relBitmask);
+ }
+ }
+ return false;
+}
+
int32_t EventHub::getScanCodeState(int32_t deviceId, int32_t scanCode) const {
if (scanCode >= 0 && scanCode <= KEY_MAX) {
AutoMutex _l(mLock);
@@ -772,6 +786,24 @@
memset(sw_bitmask, 0, sizeof(sw_bitmask));
ioctl(fd, EVIOCGBIT(EV_SW, sizeof(sw_bitmask)), sw_bitmask);
+ device->keyBitmask = new uint8_t[sizeof(key_bitmask)];
+ if (device->keyBitmask != NULL) {
+ memcpy(device->keyBitmask, key_bitmask, sizeof(key_bitmask));
+ } else {
+ delete device;
+ LOGE("out of memory allocating key bitmask");
+ return -1;
+ }
+
+ device->relBitmask = new uint8_t[sizeof(rel_bitmask)];
+ if (device->relBitmask != NULL) {
+ memcpy(device->relBitmask, rel_bitmask, sizeof(rel_bitmask));
+ } else {
+ delete device;
+ LOGE("out of memory allocating rel bitmask");
+ return -1;
+ }
+
// See if this is a keyboard. Ignore everything in the button range except for
// joystick and gamepad buttons which are handled like keyboards for the most part.
bool haveKeyboardKeys = containsNonZeroByte(key_bitmask, 0, sizeof_bit_array(BTN_MISC))
@@ -781,14 +813,6 @@
sizeof_bit_array(BTN_DIGI));
if (haveKeyboardKeys || haveGamepadButtons) {
device->classes |= INPUT_DEVICE_CLASS_KEYBOARD;
- device->keyBitmask = new uint8_t[sizeof(key_bitmask)];
- if (device->keyBitmask != NULL) {
- memcpy(device->keyBitmask, key_bitmask, sizeof(key_bitmask));
- } else {
- delete device;
- LOGE("out of memory allocating key bitmask");
- return -1;
- }
}
// See if this is a cursor device such as a trackball or mouse.
diff --git a/services/input/EventHub.h b/services/input/EventHub.h
index 35712f5..23bb344 100644
--- a/services/input/EventHub.h
+++ b/services/input/EventHub.h
@@ -167,6 +167,8 @@
virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
RawAbsoluteAxisInfo* outAxisInfo) const = 0;
+ virtual bool hasRelativeAxis(int32_t deviceId, int axis) const = 0;
+
virtual status_t mapKey(int32_t deviceId, int scancode,
int32_t* outKeycode, uint32_t* outFlags) const = 0;
@@ -224,6 +226,8 @@
virtual status_t getAbsoluteAxisInfo(int32_t deviceId, int axis,
RawAbsoluteAxisInfo* outAxisInfo) const;
+ virtual bool hasRelativeAxis(int32_t deviceId, int axis) const;
+
virtual status_t mapKey(int32_t deviceId, int scancode,
int32_t* outKeycode, uint32_t* outFlags) const;
@@ -272,6 +276,7 @@
uint32_t classes;
uint8_t* keyBitmask;
+ uint8_t* relBitmask;
String8 configurationFile;
PropertyMap* configuration;
VirtualKeyMap* virtualKeyMap;
diff --git a/services/input/InputDispatcher.cpp b/services/input/InputDispatcher.cpp
index ae11fb1..ef984d4 100644
--- a/services/input/InputDispatcher.cpp
+++ b/services/input/InputDispatcher.cpp
@@ -115,6 +115,7 @@
case AMOTION_EVENT_ACTION_CANCEL:
case AMOTION_EVENT_ACTION_MOVE:
case AMOTION_EVENT_ACTION_OUTSIDE:
+ case AMOTION_EVENT_ACTION_HOVER_MOVE:
return true;
case AMOTION_EVENT_ACTION_POINTER_DOWN:
case AMOTION_EVENT_ACTION_POINTER_UP: {
@@ -318,7 +319,8 @@
uint32_t source = motionEntry->source;
if (! isAppSwitchDue
&& motionEntry->next == & mInboundQueue.tailSentinel // exactly one event
- && motionEntry->action == AMOTION_EVENT_ACTION_MOVE
+ && (motionEntry->action == AMOTION_EVENT_ACTION_MOVE
+ || motionEntry->action == AMOTION_EVENT_ACTION_HOVER_MOVE)
&& deviceId == mThrottleState.lastDeviceId
&& source == mThrottleState.lastSource) {
nsecs_t nextTime = mThrottleState.lastEventTime
@@ -478,7 +480,8 @@
// If the application takes too long to catch up then we drop all events preceding
// the touch into the other window.
MotionEntry* motionEntry = static_cast<MotionEntry*>(entry);
- if (motionEntry->action == AMOTION_EVENT_ACTION_DOWN
+ if ((motionEntry->action == AMOTION_EVENT_ACTION_DOWN
+ || motionEntry->action == AMOTION_EVENT_ACTION_HOVER_MOVE)
&& (motionEntry->source & AINPUT_SOURCE_CLASS_POINTER)
&& mInputTargetWaitCause == INPUT_TARGET_WAIT_CAUSE_APPLICATION_NOT_READY
&& mInputTargetWaitApplication != NULL) {
@@ -838,12 +841,13 @@
bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;
// Identify targets.
+ bool conflictingPointerActions = false;
if (! mCurrentInputTargetsValid) {
int32_t injectionResult;
if (isPointerEvent) {
// Pointer event. (eg. touchscreen)
injectionResult = findTouchedWindowTargetsLocked(currentTime,
- entry, nextWakeupTime);
+ entry, nextWakeupTime, &conflictingPointerActions);
} else {
// Non touch event. (eg. trackball)
injectionResult = findFocusedWindowTargetsLocked(currentTime,
@@ -863,6 +867,10 @@
}
// Dispatch the motion.
+ if (conflictingPointerActions) {
+ synthesizeCancelationEventsForAllConnectionsLocked(
+ InputState::CANCEL_POINTER_EVENTS, "Conflicting pointer actions.");
+ }
dispatchEventToCurrentInputTargetsLocked(currentTime, entry, false);
return true;
}
@@ -1123,7 +1131,7 @@
}
int32_t InputDispatcher::findTouchedWindowTargetsLocked(nsecs_t currentTime,
- const MotionEntry* entry, nsecs_t* nextWakeupTime) {
+ const MotionEntry* entry, nsecs_t* nextWakeupTime, bool* outConflictingPointerActions) {
enum InjectionPermission {
INJECTION_PERMISSION_UNKNOWN,
INJECTION_PERMISSION_GRANTED,
@@ -1166,31 +1174,38 @@
// Update the touch state as needed based on the properties of the touch event.
int32_t injectionResult = INPUT_EVENT_INJECTION_PENDING;
InjectionPermission injectionPermission = INJECTION_PERMISSION_UNKNOWN;
- bool isSplit, wrongDevice;
- if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
- mTempTouchState.reset();
- mTempTouchState.down = true;
- mTempTouchState.deviceId = entry->deviceId;
- mTempTouchState.source = entry->source;
- isSplit = false;
- wrongDevice = false;
+
+ bool isSplit = mTouchState.split;
+ bool wrongDevice = mTouchState.down
+ && (mTouchState.deviceId != entry->deviceId
+ || mTouchState.source != entry->source);
+ if (maskedAction == AMOTION_EVENT_ACTION_DOWN
+ || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
+ bool down = maskedAction == AMOTION_EVENT_ACTION_DOWN;
+ if (wrongDevice && !down) {
+ mTempTouchState.copyFrom(mTouchState);
+ } else {
+ mTempTouchState.reset();
+ mTempTouchState.down = down;
+ mTempTouchState.deviceId = entry->deviceId;
+ mTempTouchState.source = entry->source;
+ isSplit = false;
+ wrongDevice = false;
+ }
} else {
mTempTouchState.copyFrom(mTouchState);
- isSplit = mTempTouchState.split;
- wrongDevice = mTempTouchState.down
- && (mTempTouchState.deviceId != entry->deviceId
- || mTempTouchState.source != entry->source);
- if (wrongDevice) {
+ }
+ if (wrongDevice) {
#if DEBUG_INPUT_DISPATCHER_POLICY
- LOGD("Dropping event because a pointer for a different device is already down.");
+ LOGD("Dropping event because a pointer for a different device is already down.");
#endif
- injectionResult = INPUT_EVENT_INJECTION_FAILED;
- goto Failed;
- }
+ injectionResult = INPUT_EVENT_INJECTION_FAILED;
+ goto Failed;
}
if (maskedAction == AMOTION_EVENT_ACTION_DOWN
- || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)) {
+ || (isSplit && maskedAction == AMOTION_EVENT_ACTION_POINTER_DOWN)
+ || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
/* Case 1: New splittable pointer going down. */
int32_t pointerIndex = getMotionEventActionPointerIndex(action);
@@ -1365,7 +1380,8 @@
// If this is the first pointer going down and the touched window has a wallpaper
// then also add the touched wallpaper windows so they are locked in for the duration
// of the touch gesture.
- if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
+ if (maskedAction == AMOTION_EVENT_ACTION_DOWN
+ || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
const InputWindow* foregroundWindow = mTempTouchState.getFirstForegroundWindow();
if (foregroundWindow->hasWallpaper) {
for (size_t i = 0; i < mWindows.size(); i++) {
@@ -1404,12 +1420,14 @@
if (injectionPermission == INJECTION_PERMISSION_GRANTED) {
if (!wrongDevice) {
if (maskedAction == AMOTION_EVENT_ACTION_UP
- || maskedAction == AMOTION_EVENT_ACTION_CANCEL) {
+ || maskedAction == AMOTION_EVENT_ACTION_CANCEL
+ || maskedAction == AMOTION_EVENT_ACTION_HOVER_MOVE) {
// All pointers up or canceled.
mTempTouchState.reset();
} else if (maskedAction == AMOTION_EVENT_ACTION_DOWN) {
// First pointer went down.
if (mTouchState.down) {
+ *outConflictingPointerActions = true;
#if DEBUG_FOCUS
LOGD("Pointer down received while already down.");
#endif
@@ -1750,29 +1768,7 @@
// Update the connection's input state.
EventEntry* eventEntry = dispatchEntry->eventEntry;
- InputState::Consistency consistency = connection->inputState.trackEvent(eventEntry);
-
-#if FILTER_INPUT_EVENTS
- // Filter out inconsistent sequences of input events.
- // The input system may drop or inject events in a way that could violate implicit
- // invariants on input state and potentially cause an application to crash
- // or think that a key or pointer is stuck down. Technically we make no guarantees
- // of consistency but it would be nice to improve on this where possible.
- // XXX: This code is a proof of concept only. Not ready for prime time.
- if (consistency == InputState::TOLERABLE) {
-#if DEBUG_DISPATCH_CYCLE
- LOGD("channel '%s' ~ Sending an event that is inconsistent with the connection's "
- "current input state but that is likely to be tolerated by the application.",
- connection->getInputChannelName());
-#endif
- } else if (consistency == InputState::BROKEN) {
- LOGI("channel '%s' ~ Dropping an event that is inconsistent with the connection's "
- "current input state and that is likely to cause the application to crash.",
- connection->getInputChannelName());
- startNextDispatchCycleLocked(currentTime, connection);
- return;
- }
-#endif
+ connection->inputState.trackEvent(eventEntry);
// Publish the event.
status_t status;
@@ -2307,7 +2303,8 @@
AutoMutex _l(mLock);
// Attempt batching and streaming of move events.
- if (action == AMOTION_EVENT_ACTION_MOVE) {
+ if (action == AMOTION_EVENT_ACTION_MOVE
+ || action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
// BATCHING CASE
//
// Try to append a move sample to the tail of the inbound queue for this device.
@@ -2326,7 +2323,7 @@
continue;
}
- if (motionEntry->action != AMOTION_EVENT_ACTION_MOVE
+ if (motionEntry->action != action
|| motionEntry->source != source
|| motionEntry->pointerCount != pointerCount
|| motionEntry->isInjected()) {
@@ -2385,7 +2382,7 @@
MotionEntry* motionEntry = static_cast<MotionEntry*>(
dispatchEntry->eventEntry);
- if (motionEntry->action != AMOTION_EVENT_ACTION_MOVE
+ if (motionEntry->action != action
|| motionEntry->deviceId != deviceId
|| motionEntry->source != source
|| motionEntry->pointerCount != pointerCount
@@ -3529,21 +3526,20 @@
return mKeyMementos.isEmpty() && mMotionMementos.isEmpty();
}
-InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackEvent(
+void InputDispatcher::InputState::trackEvent(
const EventEntry* entry) {
switch (entry->type) {
case EventEntry::TYPE_KEY:
- return trackKey(static_cast<const KeyEntry*>(entry));
+ trackKey(static_cast<const KeyEntry*>(entry));
+ break;
case EventEntry::TYPE_MOTION:
- return trackMotion(static_cast<const MotionEntry*>(entry));
-
- default:
- return CONSISTENT;
+ trackMotion(static_cast<const MotionEntry*>(entry));
+ break;
}
}
-InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackKey(
+void InputDispatcher::InputState::trackKey(
const KeyEntry* entry) {
int32_t action = entry->action;
for (size_t i = 0; i < mKeyMementos.size(); i++) {
@@ -3555,19 +3551,20 @@
switch (action) {
case AKEY_EVENT_ACTION_UP:
mKeyMementos.removeAt(i);
- return CONSISTENT;
+ return;
case AKEY_EVENT_ACTION_DOWN:
- return TOLERABLE;
+ mKeyMementos.removeAt(i);
+ goto Found;
default:
- return BROKEN;
+ return;
}
}
}
- switch (action) {
- case AKEY_EVENT_ACTION_DOWN: {
+Found:
+ if (action == AKEY_EVENT_ACTION_DOWN) {
mKeyMementos.push();
KeyMemento& memento = mKeyMementos.editTop();
memento.deviceId = entry->deviceId;
@@ -3576,15 +3573,10 @@
memento.scanCode = entry->scanCode;
memento.flags = entry->flags;
memento.downTime = entry->downTime;
- return CONSISTENT;
- }
-
- default:
- return BROKEN;
}
}
-InputDispatcher::InputState::Consistency InputDispatcher::InputState::trackMotion(
+void InputDispatcher::InputState::trackMotion(
const MotionEntry* entry) {
int32_t action = entry->action & AMOTION_EVENT_ACTION_MASK;
for (size_t i = 0; i < mMotionMementos.size(); i++) {
@@ -3594,40 +3586,28 @@
switch (action) {
case AMOTION_EVENT_ACTION_UP:
case AMOTION_EVENT_ACTION_CANCEL:
+ case AMOTION_EVENT_ACTION_HOVER_MOVE:
mMotionMementos.removeAt(i);
- return CONSISTENT;
+ return;
case AMOTION_EVENT_ACTION_DOWN:
- return TOLERABLE;
-
- case AMOTION_EVENT_ACTION_POINTER_DOWN:
- if (entry->pointerCount == memento.pointerCount + 1) {
- memento.setPointers(entry);
- return CONSISTENT;
- }
- return BROKEN;
+ mMotionMementos.removeAt(i);
+ goto Found;
case AMOTION_EVENT_ACTION_POINTER_UP:
- if (entry->pointerCount == memento.pointerCount - 1) {
- memento.setPointers(entry);
- return CONSISTENT;
- }
- return BROKEN;
-
+ case AMOTION_EVENT_ACTION_POINTER_DOWN:
case AMOTION_EVENT_ACTION_MOVE:
- if (entry->pointerCount == memento.pointerCount) {
- return CONSISTENT;
- }
- return BROKEN;
+ memento.setPointers(entry);
+ return;
default:
- return BROKEN;
+ return;
}
}
}
- switch (action) {
- case AMOTION_EVENT_ACTION_DOWN: {
+Found:
+ if (action == AMOTION_EVENT_ACTION_DOWN) {
mMotionMementos.push();
MotionMemento& memento = mMotionMementos.editTop();
memento.deviceId = entry->deviceId;
@@ -3636,11 +3616,6 @@
memento.yPrecision = entry->yPrecision;
memento.downTime = entry->downTime;
memento.setPointers(entry);
- return CONSISTENT;
- }
-
- default:
- return BROKEN;
}
}
diff --git a/services/input/InputDispatcher.h b/services/input/InputDispatcher.h
index 006c6b8..7abe014 100644
--- a/services/input/InputDispatcher.h
+++ b/services/input/InputDispatcher.h
@@ -593,19 +593,6 @@
* synthesized when events are dropped. */
class InputState {
public:
- // Specifies whether a given event will violate input state consistency.
- enum Consistency {
- // The event is consistent with the current input state.
- CONSISTENT,
- // The event is inconsistent with the current input state but applications
- // will tolerate it. eg. Down followed by another down.
- TOLERABLE,
- // The event is inconsistent with the current input state and will probably
- // cause applications to crash. eg. Up without prior down, move with
- // unexpected number of pointers.
- BROKEN
- };
-
// Specifies the sources to cancel.
enum CancelationOptions {
CANCEL_ALL_EVENTS = 0,
@@ -621,16 +608,13 @@
bool isNeutral() const;
// Records tracking information for an event that has just been published.
- // Returns whether the event is consistent with the current input state.
- Consistency trackEvent(const EventEntry* entry);
+ void trackEvent(const EventEntry* entry);
// Records tracking information for a key event that has just been published.
- // Returns whether the event is consistent with the current input state.
- Consistency trackKey(const KeyEntry* entry);
+ void trackKey(const KeyEntry* entry);
// Records tracking information for a motion event that has just been published.
- // Returns whether the event is consistent with the current input state.
- Consistency trackMotion(const MotionEntry* entry);
+ void trackMotion(const MotionEntry* entry);
// Synthesizes cancelation events for the current state and resets the tracked state.
void synthesizeCancelationEvents(nsecs_t currentTime, Allocator* allocator,
@@ -911,7 +895,7 @@
int32_t findFocusedWindowTargetsLocked(nsecs_t currentTime, const EventEntry* entry,
nsecs_t* nextWakeupTime);
int32_t findTouchedWindowTargetsLocked(nsecs_t currentTime, const MotionEntry* entry,
- nsecs_t* nextWakeupTime);
+ nsecs_t* nextWakeupTime, bool* outConflictingPointerActions);
void addWindowTargetLocked(const InputWindow* window, int32_t targetFlags,
BitSet32 pointerIds);
diff --git a/services/input/InputReader.cpp b/services/input/InputReader.cpp
index 47cfa05..a963c72 100644
--- a/services/input/InputReader.cpp
+++ b/services/input/InputReader.cpp
@@ -565,15 +565,6 @@
mMappers.clear();
}
-static void dumpMotionRange(String8& dump, const InputDeviceInfo& deviceInfo,
- int32_t rangeType, const char* name) {
- const InputDeviceInfo::MotionRange* range = deviceInfo.getMotionRange(rangeType);
- if (range) {
- dump.appendFormat(INDENT3 "%s: min=%0.3f, max=%0.3f, flat=%0.3f, fuzz=%0.3f\n",
- name, range->min, range->max, range->flat, range->fuzz);
- }
-}
-
void InputDevice::dump(String8& dump) {
InputDeviceInfo deviceInfo;
getDeviceInfo(& deviceInfo);
@@ -582,17 +573,24 @@
deviceInfo.getName().string());
dump.appendFormat(INDENT2 "Sources: 0x%08x\n", deviceInfo.getSources());
dump.appendFormat(INDENT2 "KeyboardType: %d\n", deviceInfo.getKeyboardType());
- if (!deviceInfo.getMotionRanges().isEmpty()) {
+
+ const KeyedVector<int32_t, InputDeviceInfo::MotionRange> ranges = deviceInfo.getMotionRanges();
+ if (!ranges.isEmpty()) {
dump.append(INDENT2 "Motion Ranges:\n");
- dumpMotionRange(dump, deviceInfo, AMOTION_EVENT_AXIS_X, "X");
- dumpMotionRange(dump, deviceInfo, AMOTION_EVENT_AXIS_Y, "Y");
- dumpMotionRange(dump, deviceInfo, AMOTION_EVENT_AXIS_PRESSURE, "Pressure");
- dumpMotionRange(dump, deviceInfo, AMOTION_EVENT_AXIS_SIZE, "Size");
- dumpMotionRange(dump, deviceInfo, AMOTION_EVENT_AXIS_TOUCH_MAJOR, "TouchMajor");
- dumpMotionRange(dump, deviceInfo, AMOTION_EVENT_AXIS_TOUCH_MINOR, "TouchMinor");
- dumpMotionRange(dump, deviceInfo, AMOTION_EVENT_AXIS_TOOL_MAJOR, "ToolMajor");
- dumpMotionRange(dump, deviceInfo, AMOTION_EVENT_AXIS_TOOL_MINOR, "ToolMinor");
- dumpMotionRange(dump, deviceInfo, AMOTION_EVENT_AXIS_ORIENTATION, "Orientation");
+ for (size_t i = 0; i < ranges.size(); i++) {
+ int32_t axis = ranges.keyAt(i);
+ const char* label = getAxisLabel(axis);
+ char name[32];
+ if (label) {
+ strncpy(name, label, sizeof(name));
+ name[sizeof(name) - 1] = '\0';
+ } else {
+ snprintf(name, sizeof(name), "%d", axis);
+ }
+ const InputDeviceInfo::MotionRange& range = ranges.valueAt(i);
+ dump.appendFormat(INDENT3 "%s: min=%0.3f, max=%0.3f, flat=%0.3f, fuzz=%0.3f\n",
+ name, range.min, range.max, range.flat, range.fuzz);
+ }
}
size_t numMappers = mMappers.size();
@@ -1123,6 +1121,9 @@
mVWheelScale = 1.0f;
mHWheelScale = 1.0f;
+
+ mHaveVWheel = getEventHub()->hasRelativeAxis(getDeviceId(), REL_WHEEL);
+ mHaveHWheel = getEventHub()->hasRelativeAxis(getDeviceId(), REL_HWHEEL);
}
void CursorInputMapper::configureParameters() {
@@ -1274,8 +1275,10 @@
if (downChanged) {
motionEventAction = mLocked.down ? AMOTION_EVENT_ACTION_DOWN : AMOTION_EVENT_ACTION_UP;
- } else {
+ } else if (mLocked.down || mPointerController == NULL) {
motionEventAction = AMOTION_EVENT_ACTION_MOVE;
+ } else {
+ motionEventAction = AMOTION_EVENT_ACTION_HOVER_MOVE;
}
if (mParameters.orientationAware && mParameters.associatedDisplayId >= 0
diff --git a/services/input/tests/InputReader_test.cpp b/services/input/tests/InputReader_test.cpp
index 648250e..fac71bb 100644
--- a/services/input/tests/InputReader_test.cpp
+++ b/services/input/tests/InputReader_test.cpp
@@ -561,6 +561,10 @@
return -1;
}
+ virtual bool hasRelativeAxis(int32_t deviceId, int axis) const {
+ return false;
+ }
+
virtual status_t mapKey(int32_t deviceId, int scancode,
int32_t* outKeycode, uint32_t* outFlags) const {
Device* device = getDevice(deviceId);