replicant-frameworks_native/libs/ui/InputReader.cpp
Jeff Brown e839a589bf Native input dispatch rewrite work in progress.
The old dispatch mechanism has been left in place and continues to
be used by default for now.  To enable native input dispatch,
edit the ENABLE_NATIVE_DISPATCH constant in WindowManagerPolicy.

Includes part of the new input event NDK API.  Some details TBD.

To wire up input dispatch, as the ViewRoot adds a window to the
window session it receives an InputChannel object as an output
argument.  The InputChannel encapsulates the file descriptors for a
shared memory region and two pipe end-points.  The ViewRoot then
provides the InputChannel to the InputQueue.  Behind the
scenes, InputQueue simply attaches handlers to the native PollLoop object
that underlies the MessageQueue.  This way MessageQueue doesn't need
to know anything about input dispatch per-se, it just exposes (in native
code) a PollLoop that other components can use to monitor file descriptor
state changes.

There can be zero or more targets for any given input event.  Each
input target is specified by its input channel and some parameters
including flags, an X/Y coordinate offset, and the dispatch timeout.
An input target can request either synchronous dispatch (for foreground apps)
or asynchronous dispatch (fire-and-forget for wallpapers and "outside"
targets).  Currently, finding the appropriate input targets for an event
requires a call back into the WindowManagerServer from native code.
In the future this will be refactored to avoid most of these callbacks
except as required to handle pending focus transitions.

End-to-end event dispatch mostly works!

To do: event injection, rate limiting, ANRs, testing, optimization, etc.

Change-Id: I8c36b2b9e0a2d27392040ecda0f51b636456de25
2010-06-13 17:42:16 -07:00

1845 lines
67 KiB
C++

//
// Copyright 2010 The Android Open Source Project
//
// The input reader.
//
#define LOG_TAG "InputReader"
//#define LOG_NDEBUG 0
// Log debug messages for each raw event received from the EventHub.
#define DEBUG_RAW_EVENTS 0
// Log debug messages about touch screen filtering hacks.
#define DEBUG_HACKS 1
// Log debug messages about virtual key processing.
#define DEBUG_VIRTUAL_KEYS 1
// Log debug messages about pointers.
#define DEBUG_POINTERS 1
#include <cutils/log.h>
#include <ui/InputReader.h>
#include <stddef.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <limits.h>
namespace android {
// --- Static Functions ---
template<typename T>
inline static T abs(const T& value) {
return value < 0 ? - value : value;
}
template<typename T>
inline static T min(const T& a, const T& b) {
return a < b ? a : b;
}
int32_t updateMetaState(int32_t keyCode, bool down, int32_t oldMetaState) {
int32_t mask;
switch (keyCode) {
case KEYCODE_ALT_LEFT:
mask = META_ALT_LEFT_ON;
break;
case KEYCODE_ALT_RIGHT:
mask = META_ALT_RIGHT_ON;
break;
case KEYCODE_SHIFT_LEFT:
mask = META_SHIFT_LEFT_ON;
break;
case KEYCODE_SHIFT_RIGHT:
mask = META_SHIFT_RIGHT_ON;
break;
case KEYCODE_SYM:
mask = META_SYM_ON;
break;
default:
return oldMetaState;
}
int32_t newMetaState = down ? oldMetaState | mask : oldMetaState & ~ mask
& ~ (META_ALT_ON | META_SHIFT_ON);
if (newMetaState & (META_ALT_LEFT_ON | META_ALT_RIGHT_ON)) {
newMetaState |= META_ALT_ON;
}
if (newMetaState & (META_SHIFT_LEFT_ON | META_SHIFT_RIGHT_ON)) {
newMetaState |= META_SHIFT_ON;
}
return newMetaState;
}
static const int32_t keyCodeRotationMap[][4] = {
// key codes enumerated counter-clockwise with the original (unrotated) key first
// no rotation, 90 degree rotation, 180 degree rotation, 270 degree rotation
{ KEYCODE_DPAD_DOWN, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_UP, KEYCODE_DPAD_LEFT },
{ KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_UP, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_DOWN },
{ KEYCODE_DPAD_UP, KEYCODE_DPAD_LEFT, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_RIGHT },
{ KEYCODE_DPAD_LEFT, KEYCODE_DPAD_DOWN, KEYCODE_DPAD_RIGHT, KEYCODE_DPAD_UP },
};
static const int keyCodeRotationMapSize =
sizeof(keyCodeRotationMap) / sizeof(keyCodeRotationMap[0]);
int32_t rotateKeyCode(int32_t keyCode, int32_t orientation) {
if (orientation != InputDispatchPolicyInterface::ROTATION_0) {
for (int i = 0; i < keyCodeRotationMapSize; i++) {
if (keyCode == keyCodeRotationMap[i][0]) {
return keyCodeRotationMap[i][orientation];
}
}
}
return keyCode;
}
// --- InputDevice ---
InputDevice::InputDevice(int32_t id, uint32_t classes, String8 name) :
id(id), classes(classes), name(name), ignored(false) {
}
void InputDevice::reset() {
if (isKeyboard()) {
keyboard.reset();
}
if (isTrackball()) {
trackball.reset();
}
if (isMultiTouchScreen()) {
multiTouchScreen.reset();
} else if (isSingleTouchScreen()) {
singleTouchScreen.reset();
}
if (isTouchScreen()) {
touchScreen.reset();
}
}
// --- InputDevice::TouchData ---
void InputDevice::TouchData::copyFrom(const TouchData& other) {
pointerCount = other.pointerCount;
idBits = other.idBits;
for (uint32_t i = 0; i < pointerCount; i++) {
pointers[i] = other.pointers[i];
idToIndex[i] = other.idToIndex[i];
}
}
// --- InputDevice::KeyboardState ---
void InputDevice::KeyboardState::reset() {
current.metaState = META_NONE;
current.downTime = 0;
}
// --- InputDevice::TrackballState ---
void InputDevice::TrackballState::reset() {
accumulator.clear();
current.down = false;
current.downTime = 0;
}
// --- InputDevice::TouchScreenState ---
void InputDevice::TouchScreenState::reset() {
lastTouch.clear();
downTime = 0;
currentVirtualKey.down = false;
for (uint32_t i = 0; i < MAX_POINTERS; i++) {
averagingTouchFilter.historyStart[i] = 0;
averagingTouchFilter.historyEnd[i] = 0;
}
jumpyTouchFilter.jumpyPointsDropped = 0;
}
void InputDevice::TouchScreenState::calculatePointerIds() {
uint32_t currentPointerCount = currentTouch.pointerCount;
uint32_t lastPointerCount = lastTouch.pointerCount;
if (currentPointerCount == 0) {
// No pointers to assign.
currentTouch.idBits.clear();
} else if (lastPointerCount == 0) {
// All pointers are new.
currentTouch.idBits.clear();
for (uint32_t i = 0; i < currentPointerCount; i++) {
currentTouch.pointers[i].id = i;
currentTouch.idToIndex[i] = i;
currentTouch.idBits.markBit(i);
}
} else if (currentPointerCount == 1 && lastPointerCount == 1) {
// Only one pointer and no change in count so it must have the same id as before.
uint32_t id = lastTouch.pointers[0].id;
currentTouch.pointers[0].id = id;
currentTouch.idToIndex[id] = 0;
currentTouch.idBits.value = BitSet32::valueForBit(id);
} else {
// General case.
// We build a heap of squared euclidean distances between current and last pointers
// associated with the current and last pointer indices. Then, we find the best
// match (by distance) for each current pointer.
struct {
uint32_t currentPointerIndex : 8;
uint32_t lastPointerIndex : 8;
uint64_t distance : 48; // squared distance
} heap[MAX_POINTERS * MAX_POINTERS];
uint32_t heapSize = 0;
for (uint32_t currentPointerIndex = 0; currentPointerIndex < currentPointerCount;
currentPointerIndex++) {
for (uint32_t lastPointerIndex = 0; lastPointerIndex < lastPointerCount;
lastPointerIndex++) {
int64_t deltaX = currentTouch.pointers[currentPointerIndex].x
- lastTouch.pointers[lastPointerIndex].x;
int64_t deltaY = currentTouch.pointers[currentPointerIndex].y
- lastTouch.pointers[lastPointerIndex].y;
uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY);
// Insert new element into the heap (sift up).
heapSize += 1;
uint32_t insertionIndex = heapSize;
while (insertionIndex > 1) {
uint32_t parentIndex = (insertionIndex - 1) / 2;
if (distance < heap[parentIndex].distance) {
heap[insertionIndex] = heap[parentIndex];
insertionIndex = parentIndex;
} else {
break;
}
}
heap[insertionIndex].currentPointerIndex = currentPointerIndex;
heap[insertionIndex].lastPointerIndex = lastPointerIndex;
heap[insertionIndex].distance = distance;
}
}
// Pull matches out by increasing order of distance.
// To avoid reassigning pointers that have already been matched, the loop keeps track
// of which last and current pointers have been matched using the matchedXXXBits variables.
// It also tracks the used pointer id bits.
BitSet32 matchedLastBits(0);
BitSet32 matchedCurrentBits(0);
BitSet32 usedIdBits(0);
bool first = true;
for (uint32_t i = min(currentPointerCount, lastPointerCount); i > 0; i--) {
for (;;) {
if (first) {
// The first time through the loop, we just consume the root element of
// the heap (the one with smalled distance).
first = false;
} else {
// Previous iterations consumed the root element of the heap.
// Pop root element off of the heap (sift down).
heapSize -= 1;
assert(heapSize > 0);
// Sift down to find where the element at index heapSize needs to be moved.
uint32_t rootIndex = 0;
for (;;) {
uint32_t childIndex = rootIndex * 2 + 1;
if (childIndex >= heapSize) {
break;
}
if (childIndex + 1 < heapSize
&& heap[childIndex + 1].distance < heap[childIndex].distance) {
childIndex += 1;
}
if (heap[heapSize].distance < heap[childIndex].distance) {
break;
}
heap[rootIndex] = heap[childIndex];
rootIndex = childIndex;
}
heap[rootIndex] = heap[heapSize];
}
uint32_t currentPointerIndex = heap[0].currentPointerIndex;
if (matchedCurrentBits.hasBit(currentPointerIndex)) continue; // already matched
uint32_t lastPointerIndex = heap[0].lastPointerIndex;
if (matchedLastBits.hasBit(lastPointerIndex)) continue; // already matched
matchedCurrentBits.markBit(currentPointerIndex);
matchedLastBits.markBit(lastPointerIndex);
uint32_t id = lastTouch.pointers[lastPointerIndex].id;
currentTouch.pointers[currentPointerIndex].id = id;
currentTouch.idToIndex[id] = currentPointerIndex;
usedIdBits.markBit(id);
break;
}
}
// Assign fresh ids to new pointers.
if (currentPointerCount > lastPointerCount) {
for (uint32_t i = currentPointerCount - lastPointerCount; ;) {
uint32_t currentPointerIndex = matchedCurrentBits.firstUnmarkedBit();
uint32_t id = usedIdBits.firstUnmarkedBit();
currentTouch.pointers[currentPointerIndex].id = id;
currentTouch.idToIndex[id] = currentPointerIndex;
usedIdBits.markBit(id);
if (--i == 0) break; // done
matchedCurrentBits.markBit(currentPointerIndex);
}
}
// Fix id bits.
currentTouch.idBits = usedIdBits;
}
}
/* Special hack for devices that have bad screen data: if one of the
* points has moved more than a screen height from the last position,
* then drop it. */
bool InputDevice::TouchScreenState::applyBadTouchFilter() {
uint32_t pointerCount = currentTouch.pointerCount;
// Nothing to do if there are no points.
if (pointerCount == 0) {
return false;
}
// Don't do anything if a finger is going down or up. We run
// here before assigning pointer IDs, so there isn't a good
// way to do per-finger matching.
if (pointerCount != lastTouch.pointerCount) {
return false;
}
// We consider a single movement across more than a 7/16 of
// the long size of the screen to be bad. This was a magic value
// determined by looking at the maximum distance it is feasible
// to actually move in one sample.
int32_t maxDeltaY = parameters.yAxis.range * 7 / 16;
// XXX The original code in InputDevice.java included commented out
// code for testing the X axis. Note that when we drop a point
// we don't actually restore the old X either. Strange.
// The old code also tries to track when bad points were previously
// detected but it turns out that due to the placement of a "break"
// at the end of the loop, we never set mDroppedBadPoint to true
// so it is effectively dead code.
// Need to figure out if the old code is busted or just overcomplicated
// but working as intended.
// Look through all new points and see if any are farther than
// acceptable from all previous points.
for (uint32_t i = pointerCount; i-- > 0; ) {
int32_t y = currentTouch.pointers[i].y;
int32_t closestY = INT_MAX;
int32_t closestDeltaY = 0;
#if DEBUG_HACKS
LOGD("BadTouchFilter: Looking at next point #%d: y=%d", i, y);
#endif
for (uint32_t j = pointerCount; j-- > 0; ) {
int32_t lastY = lastTouch.pointers[j].y;
int32_t deltaY = abs(y - lastY);
#if DEBUG_HACKS
LOGD("BadTouchFilter: Comparing with last point #%d: y=%d deltaY=%d",
j, lastY, deltaY);
#endif
if (deltaY < maxDeltaY) {
goto SkipSufficientlyClosePoint;
}
if (deltaY < closestDeltaY) {
closestDeltaY = deltaY;
closestY = lastY;
}
}
// Must not have found a close enough match.
#if DEBUG_HACKS
LOGD("BadTouchFilter: Dropping bad point #%d: newY=%d oldY=%d deltaY=%d maxDeltaY=%d",
i, y, closestY, closestDeltaY, maxDeltaY);
#endif
currentTouch.pointers[i].y = closestY;
return true; // XXX original code only corrects one point
SkipSufficientlyClosePoint: ;
}
// No change.
return false;
}
/* Special hack for devices that have bad screen data: drop points where
* the coordinate value for one axis has jumped to the other pointer's location.
*/
bool InputDevice::TouchScreenState::applyJumpyTouchFilter() {
uint32_t pointerCount = currentTouch.pointerCount;
if (lastTouch.pointerCount != pointerCount) {
#if DEBUG_HACKS
LOGD("JumpyTouchFilter: Different pointer count %d -> %d",
lastTouch.pointerCount, pointerCount);
for (uint32_t i = 0; i < pointerCount; i++) {
LOGD(" Pointer %d (%d, %d)", i,
currentTouch.pointers[i].x, currentTouch.pointers[i].y);
}
#endif
if (jumpyTouchFilter.jumpyPointsDropped < JUMPY_TRANSITION_DROPS) {
if (lastTouch.pointerCount == 1 && pointerCount == 2) {
// Just drop the first few events going from 1 to 2 pointers.
// They're bad often enough that they're not worth considering.
currentTouch.pointerCount = 1;
jumpyTouchFilter.jumpyPointsDropped += 1;
#if DEBUG_HACKS
LOGD("JumpyTouchFilter: Pointer 2 dropped");
#endif
return true;
} else if (lastTouch.pointerCount == 2 && pointerCount == 1) {
// The event when we go from 2 -> 1 tends to be messed up too
currentTouch.pointerCount = 2;
currentTouch.pointers[0] = lastTouch.pointers[0];
currentTouch.pointers[1] = lastTouch.pointers[1];
jumpyTouchFilter.jumpyPointsDropped += 1;
#if DEBUG_HACKS
for (int32_t i = 0; i < 2; i++) {
LOGD("JumpyTouchFilter: Pointer %d replaced (%d, %d)", i,
currentTouch.pointers[i].x, currentTouch.pointers[i].y);
}
#endif
return true;
}
}
// Reset jumpy points dropped on other transitions or if limit exceeded.
jumpyTouchFilter.jumpyPointsDropped = 0;
#if DEBUG_HACKS
LOGD("JumpyTouchFilter: Transition - drop limit reset");
#endif
return false;
}
// We have the same number of pointers as last time.
// A 'jumpy' point is one where the coordinate value for one axis
// has jumped to the other pointer's location. No need to do anything
// else if we only have one pointer.
if (pointerCount < 2) {
return false;
}
if (jumpyTouchFilter.jumpyPointsDropped < JUMPY_DROP_LIMIT) {
int jumpyEpsilon = parameters.yAxis.range / JUMPY_EPSILON_DIVISOR;
// We only replace the single worst jumpy point as characterized by pointer distance
// in a single axis.
int32_t badPointerIndex = -1;
int32_t badPointerReplacementIndex = -1;
int32_t badPointerDistance = INT_MIN; // distance to be corrected
for (uint32_t i = pointerCount; i-- > 0; ) {
int32_t x = currentTouch.pointers[i].x;
int32_t y = currentTouch.pointers[i].y;
#if DEBUG_HACKS
LOGD("JumpyTouchFilter: Point %d (%d, %d)", i, x, y);
#endif
// Check if a touch point is too close to another's coordinates
bool dropX = false, dropY = false;
for (uint32_t j = 0; j < pointerCount; j++) {
if (i == j) {
continue;
}
if (abs(x - currentTouch.pointers[j].x) <= jumpyEpsilon) {
dropX = true;
break;
}
if (abs(y - currentTouch.pointers[j].y) <= jumpyEpsilon) {
dropY = true;
break;
}
}
if (! dropX && ! dropY) {
continue; // not jumpy
}
// Find a replacement candidate by comparing with older points on the
// complementary (non-jumpy) axis.
int32_t distance = INT_MIN; // distance to be corrected
int32_t replacementIndex = -1;
if (dropX) {
// X looks too close. Find an older replacement point with a close Y.
int32_t smallestDeltaY = INT_MAX;
for (uint32_t j = 0; j < pointerCount; j++) {
int32_t deltaY = abs(y - lastTouch.pointers[j].y);
if (deltaY < smallestDeltaY) {
smallestDeltaY = deltaY;
replacementIndex = j;
}
}
distance = abs(x - lastTouch.pointers[replacementIndex].x);
} else {
// Y looks too close. Find an older replacement point with a close X.
int32_t smallestDeltaX = INT_MAX;
for (uint32_t j = 0; j < pointerCount; j++) {
int32_t deltaX = abs(x - lastTouch.pointers[j].x);
if (deltaX < smallestDeltaX) {
smallestDeltaX = deltaX;
replacementIndex = j;
}
}
distance = abs(y - lastTouch.pointers[replacementIndex].y);
}
// If replacing this pointer would correct a worse error than the previous ones
// considered, then use this replacement instead.
if (distance > badPointerDistance) {
badPointerIndex = i;
badPointerReplacementIndex = replacementIndex;
badPointerDistance = distance;
}
}
// Correct the jumpy pointer if one was found.
if (badPointerIndex >= 0) {
#if DEBUG_HACKS
LOGD("JumpyTouchFilter: Replacing bad pointer %d with (%d, %d)",
badPointerIndex,
lastTouch.pointers[badPointerReplacementIndex].x,
lastTouch.pointers[badPointerReplacementIndex].y);
#endif
currentTouch.pointers[badPointerIndex].x =
lastTouch.pointers[badPointerReplacementIndex].x;
currentTouch.pointers[badPointerIndex].y =
lastTouch.pointers[badPointerReplacementIndex].y;
jumpyTouchFilter.jumpyPointsDropped += 1;
return true;
}
}
jumpyTouchFilter.jumpyPointsDropped = 0;
return false;
}
/* Special hack for devices that have bad screen data: aggregate and
* compute averages of the coordinate data, to reduce the amount of
* jitter seen by applications. */
void InputDevice::TouchScreenState::applyAveragingTouchFilter() {
for (uint32_t currentIndex = 0; currentIndex < currentTouch.pointerCount; currentIndex++) {
uint32_t id = currentTouch.pointers[currentIndex].id;
int32_t x = currentTouch.pointers[currentIndex].x;
int32_t y = currentTouch.pointers[currentIndex].y;
int32_t pressure = currentTouch.pointers[currentIndex].pressure;
if (lastTouch.idBits.hasBit(id)) {
// Pointer still down compute average.
uint32_t start = averagingTouchFilter.historyStart[id];
uint32_t end = averagingTouchFilter.historyEnd[id];
int64_t deltaX = x - averagingTouchFilter.historyData[end].pointers[id].x;
int64_t deltaY = y - averagingTouchFilter.historyData[end].pointers[id].y;
uint64_t distance = uint64_t(deltaX * deltaX + deltaY * deltaY);
#if DEBUG_HACKS
LOGD("AveragingTouchFilter: Pointer id %d - Distance from last sample: %lld",
id, distance);
#endif
if (distance < AVERAGING_DISTANCE_LIMIT) {
end += 1;
if (end > AVERAGING_HISTORY_SIZE) {
end = 0;
}
if (end == start) {
start += 1;
if (start > AVERAGING_HISTORY_SIZE) {
start = 0;
}
}
averagingTouchFilter.historyStart[id] = start;
averagingTouchFilter.historyEnd[id] = end;
averagingTouchFilter.historyData[end].pointers[id].x = x;
averagingTouchFilter.historyData[end].pointers[id].y = y;
averagingTouchFilter.historyData[end].pointers[id].pressure = pressure;
int32_t averagedX = 0;
int32_t averagedY = 0;
int32_t totalPressure = 0;
for (;;) {
int32_t historicalX = averagingTouchFilter.historyData[start].pointers[id].x;
int32_t historicalY = averagingTouchFilter.historyData[start].pointers[id].x;
int32_t historicalPressure = averagingTouchFilter.historyData[start]
.pointers[id].pressure;
averagedX += historicalX;
averagedY += historicalY;
totalPressure += historicalPressure;
if (start == end) {
break;
}
start += 1;
if (start > AVERAGING_HISTORY_SIZE) {
start = 0;
}
}
averagedX /= totalPressure;
averagedY /= totalPressure;
#if DEBUG_HACKS
LOGD("AveragingTouchFilter: Pointer id %d - "
"totalPressure=%d, averagedX=%d, averagedY=%d", id, totalPressure,
averagedX, averagedY);
#endif
currentTouch.pointers[currentIndex].x = averagedX;
currentTouch.pointers[currentIndex].y = averagedY;
} else {
#if DEBUG_HACKS
LOGD("AveragingTouchFilter: Pointer id %d - Exceeded max distance", id);
#endif
}
} else {
#if DEBUG_HACKS
LOGD("AveragingTouchFilter: Pointer id %d - Pointer went up", id);
#endif
}
// Reset pointer history.
averagingTouchFilter.historyStart[id] = 0;
averagingTouchFilter.historyEnd[id] = 0;
averagingTouchFilter.historyData[0].pointers[id].x = x;
averagingTouchFilter.historyData[0].pointers[id].y = y;
averagingTouchFilter.historyData[0].pointers[id].pressure = pressure;
}
}
bool InputDevice::TouchScreenState::isPointInsideDisplay(int32_t x, int32_t y) const {
return x >= parameters.xAxis.minValue
&& x <= parameters.xAxis.maxValue
&& y >= parameters.yAxis.minValue
&& y <= parameters.yAxis.maxValue;
}
// --- InputDevice::SingleTouchScreenState ---
void InputDevice::SingleTouchScreenState::reset() {
accumulator.clear();
current.down = false;
current.x = 0;
current.y = 0;
current.pressure = 0;
current.size = 0;
}
// --- InputDevice::MultiTouchScreenState ---
void InputDevice::MultiTouchScreenState::reset() {
accumulator.clear();
}
// --- InputReader ---
InputReader::InputReader(const sp<EventHubInterface>& eventHub,
const sp<InputDispatchPolicyInterface>& policy,
const sp<InputDispatcherInterface>& dispatcher) :
mEventHub(eventHub), mPolicy(policy), mDispatcher(dispatcher) {
resetGlobalMetaState();
resetDisplayProperties();
updateGlobalVirtualKeyState();
}
InputReader::~InputReader() {
for (size_t i = 0; i < mDevices.size(); i++) {
delete mDevices.valueAt(i);
}
}
void InputReader::loopOnce() {
RawEvent rawEvent;
mEventHub->getEvent(& rawEvent.deviceId, & rawEvent.type, & rawEvent.scanCode,
& rawEvent.keyCode, & rawEvent.flags, & rawEvent.value, & rawEvent.when);
// Replace the event timestamp so it is in same timebase as java.lang.System.nanoTime()
// and android.os.SystemClock.uptimeMillis() as expected by the rest of the system.
rawEvent.when = systemTime(SYSTEM_TIME_MONOTONIC);
#if DEBUG_RAW_EVENTS
LOGD("Input event: device=0x%x type=0x%x scancode=%d keycode=%d value=%d",
rawEvent.deviceId, rawEvent.type, rawEvent.scanCode, rawEvent.keyCode,
rawEvent.value);
#endif
process(& rawEvent);
}
void InputReader::process(const RawEvent* rawEvent) {
switch (rawEvent->type) {
case EventHubInterface::DEVICE_ADDED:
handleDeviceAdded(rawEvent);
break;
case EventHubInterface::DEVICE_REMOVED:
handleDeviceRemoved(rawEvent);
break;
case EV_SYN:
handleSync(rawEvent);
break;
case EV_KEY:
handleKey(rawEvent);
break;
case EV_REL:
handleRelativeMotion(rawEvent);
break;
case EV_ABS:
handleAbsoluteMotion(rawEvent);
break;
case EV_SW:
handleSwitch(rawEvent);
break;
}
}
void InputReader::handleDeviceAdded(const RawEvent* rawEvent) {
InputDevice* device = getDevice(rawEvent->deviceId);
if (device) {
LOGW("Ignoring spurious device added event for deviceId %d.", rawEvent->deviceId);
return;
}
addDevice(rawEvent->when, rawEvent->deviceId);
}
void InputReader::handleDeviceRemoved(const RawEvent* rawEvent) {
InputDevice* device = getDevice(rawEvent->deviceId);
if (! device) {
LOGW("Ignoring spurious device removed event for deviceId %d.", rawEvent->deviceId);
return;
}
removeDevice(rawEvent->when, device);
}
void InputReader::handleSync(const RawEvent* rawEvent) {
InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId);
if (! device) return;
if (rawEvent->scanCode == SYN_MT_REPORT) {
// MultiTouch Sync: The driver has returned all data for *one* of the pointers.
// We drop pointers with pressure <= 0 since that indicates they are not down.
if (device->isMultiTouchScreen()) {
uint32_t pointerIndex = device->multiTouchScreen.accumulator.pointerCount;
if (device->multiTouchScreen.accumulator.pointers[pointerIndex].fields) {
if (pointerIndex == MAX_POINTERS) {
LOGW("MultiTouch device driver returned more than maximum of %d pointers.",
MAX_POINTERS);
} else {
pointerIndex += 1;
device->multiTouchScreen.accumulator.pointerCount = pointerIndex;
}
}
device->multiTouchScreen.accumulator.pointers[pointerIndex].clear();
}
} else if (rawEvent->scanCode == SYN_REPORT) {
// General Sync: The driver has returned all data for the current event update.
if (device->isMultiTouchScreen()) {
if (device->multiTouchScreen.accumulator.isDirty()) {
onMultiTouchScreenStateChanged(rawEvent->when, device);
device->multiTouchScreen.accumulator.clear();
}
} else if (device->isSingleTouchScreen()) {
if (device->singleTouchScreen.accumulator.isDirty()) {
onSingleTouchScreenStateChanged(rawEvent->when, device);
device->singleTouchScreen.accumulator.clear();
}
}
if (device->trackball.accumulator.isDirty()) {
onTrackballStateChanged(rawEvent->when, device);
device->trackball.accumulator.clear();
}
}
}
void InputReader::handleKey(const RawEvent* rawEvent) {
InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId);
if (! device) return;
bool down = rawEvent->value != 0;
int32_t scanCode = rawEvent->scanCode;
if (device->isKeyboard() && (scanCode < BTN_FIRST || scanCode > BTN_LAST)) {
int32_t keyCode = rawEvent->keyCode;
onKey(rawEvent->when, device, down, keyCode, scanCode, rawEvent->flags);
} else if (device->isSingleTouchScreen()) {
switch (rawEvent->scanCode) {
case BTN_TOUCH:
device->singleTouchScreen.accumulator.fields |=
InputDevice::SingleTouchScreenState::Accumulator::FIELD_BTN_TOUCH;
device->singleTouchScreen.accumulator.btnTouch = down;
break;
}
} else if (device->isTrackball()) {
switch (rawEvent->scanCode) {
case BTN_MOUSE:
device->trackball.accumulator.fields |=
InputDevice::TrackballState::Accumulator::FIELD_BTN_MOUSE;
device->trackball.accumulator.btnMouse = down;
// send the down immediately
// XXX this emulates the old behavior of KeyInputQueue, unclear whether it is
// necessary or if we can wait until the next sync
onTrackballStateChanged(rawEvent->when, device);
device->trackball.accumulator.clear();
break;
}
}
}
void InputReader::handleRelativeMotion(const RawEvent* rawEvent) {
InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId);
if (! device) return;
if (device->isTrackball()) {
switch (rawEvent->scanCode) {
case REL_X:
device->trackball.accumulator.fields |=
InputDevice::TrackballState::Accumulator::FIELD_REL_X;
device->trackball.accumulator.relX = rawEvent->value;
break;
case REL_Y:
device->trackball.accumulator.fields |=
InputDevice::TrackballState::Accumulator::FIELD_REL_Y;
device->trackball.accumulator.relY = rawEvent->value;
break;
}
}
}
void InputReader::handleAbsoluteMotion(const RawEvent* rawEvent) {
InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId);
if (! device) return;
if (device->isMultiTouchScreen()) {
uint32_t pointerIndex = device->multiTouchScreen.accumulator.pointerCount;
InputDevice::MultiTouchScreenState::Accumulator::Pointer* pointer =
& device->multiTouchScreen.accumulator.pointers[pointerIndex];
switch (rawEvent->scanCode) {
case ABS_MT_POSITION_X:
pointer->fields |=
InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_X;
pointer->absMTPositionX = rawEvent->value;
break;
case ABS_MT_POSITION_Y:
pointer->fields |=
InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_Y;
pointer->absMTPositionY = rawEvent->value;
break;
case ABS_MT_TOUCH_MAJOR:
pointer->fields |=
InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TOUCH_MAJOR;
pointer->absMTTouchMajor = rawEvent->value;
break;
case ABS_MT_WIDTH_MAJOR:
pointer->fields |=
InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_WIDTH_MAJOR;
pointer->absMTWidthMajor = rawEvent->value;
break;
case ABS_MT_TRACKING_ID:
pointer->fields |=
InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TRACKING_ID;
pointer->absMTTrackingId = rawEvent->value;
break;
}
} else if (device->isSingleTouchScreen()) {
switch (rawEvent->scanCode) {
case ABS_X:
device->singleTouchScreen.accumulator.fields |=
InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_X;
device->singleTouchScreen.accumulator.absX = rawEvent->value;
break;
case ABS_Y:
device->singleTouchScreen.accumulator.fields |=
InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_Y;
device->singleTouchScreen.accumulator.absY = rawEvent->value;
break;
case ABS_PRESSURE:
device->singleTouchScreen.accumulator.fields |=
InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_PRESSURE;
device->singleTouchScreen.accumulator.absPressure = rawEvent->value;
break;
case ABS_TOOL_WIDTH:
device->singleTouchScreen.accumulator.fields |=
InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_TOOL_WIDTH;
device->singleTouchScreen.accumulator.absToolWidth = rawEvent->value;
break;
}
}
}
void InputReader::handleSwitch(const RawEvent* rawEvent) {
InputDevice* device = getNonIgnoredDevice(rawEvent->deviceId);
if (! device) return;
onSwitch(rawEvent->when, device, rawEvent->value != 0, rawEvent->scanCode);
}
void InputReader::onKey(nsecs_t when, InputDevice* device,
bool down, int32_t keyCode, int32_t scanCode, uint32_t policyFlags) {
/* Refresh display properties so we can rotate key codes according to display orientation */
if (! refreshDisplayProperties()) {
return;
}
/* Update device state */
int32_t oldMetaState = device->keyboard.current.metaState;
int32_t newMetaState = updateMetaState(keyCode, down, oldMetaState);
if (oldMetaState != newMetaState) {
device->keyboard.current.metaState = newMetaState;
resetGlobalMetaState();
}
// FIXME if we send a down event about a rotated key press we should ensure that we send
// a corresponding up event about the rotated key press even if the orientation
// has changed in the meantime
keyCode = rotateKeyCode(keyCode, mDisplayOrientation);
if (down) {
device->keyboard.current.downTime = when;
}
/* Apply policy */
int32_t policyActions = mPolicy->interceptKey(when, device->id,
down, keyCode, scanCode, policyFlags);
if (! applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) {
return; // event dropped
}
/* Enqueue key event for dispatch */
int32_t keyEventAction;
if (down) {
device->keyboard.current.downTime = when;
keyEventAction = KEY_EVENT_ACTION_DOWN;
} else {
keyEventAction = KEY_EVENT_ACTION_UP;
}
int32_t keyEventFlags = KEY_EVENT_FLAG_FROM_SYSTEM;
if (policyActions & InputDispatchPolicyInterface::ACTION_WOKE_HERE) {
keyEventFlags = keyEventFlags | KEY_EVENT_FLAG_WOKE_HERE;
}
mDispatcher->notifyKey(when, device->id, INPUT_EVENT_NATURE_KEY, policyFlags,
keyEventAction, keyEventFlags, keyCode, scanCode,
device->keyboard.current.metaState,
device->keyboard.current.downTime);
}
void InputReader::onSwitch(nsecs_t when, InputDevice* device, bool down,
int32_t code) {
switch (code) {
case SW_LID:
mDispatcher->notifyLidSwitchChanged(when, ! down);
}
}
void InputReader::onMultiTouchScreenStateChanged(nsecs_t when,
InputDevice* device) {
static const uint32_t REQUIRED_FIELDS =
InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_X
| InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_POSITION_Y
| InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_TOUCH_MAJOR
| InputDevice::MultiTouchScreenState::Accumulator::FIELD_ABS_MT_WIDTH_MAJOR;
/* Refresh display properties so we can map touch screen coords into display coords */
if (! refreshDisplayProperties()) {
return;
}
/* Update device state */
InputDevice::MultiTouchScreenState* in = & device->multiTouchScreen;
InputDevice::TouchData* out = & device->touchScreen.currentTouch;
uint32_t inCount = in->accumulator.pointerCount;
uint32_t outCount = 0;
bool havePointerIds = true;
out->clear();
for (uint32_t inIndex = 0; inIndex < inCount; inIndex++) {
uint32_t fields = in->accumulator.pointers[inIndex].fields;
if ((fields & REQUIRED_FIELDS) != REQUIRED_FIELDS) {
#if DEBUG_POINTERS
LOGD("Pointers: Missing required multitouch pointer fields: index=%d, fields=%d",
inIndex, fields);
continue;
#endif
}
if (in->accumulator.pointers[inIndex].absMTTouchMajor <= 0) {
// Pointer is not down. Drop it.
continue;
}
// FIXME assignment of pressure may be incorrect, probably better to let
// pressure = touch / width. Later on we pass width to MotionEvent as a size, which
// isn't quite right either. Should be using touch for that.
out->pointers[outCount].x = in->accumulator.pointers[inIndex].absMTPositionX;
out->pointers[outCount].y = in->accumulator.pointers[inIndex].absMTPositionY;
out->pointers[outCount].pressure = in->accumulator.pointers[inIndex].absMTTouchMajor;
out->pointers[outCount].size = in->accumulator.pointers[inIndex].absMTWidthMajor;
if (havePointerIds) {
if (fields & InputDevice::MultiTouchScreenState::Accumulator::
FIELD_ABS_MT_TRACKING_ID) {
uint32_t id = uint32_t(in->accumulator.pointers[inIndex].absMTTrackingId);
if (id > MAX_POINTER_ID) {
#if DEBUG_POINTERS
LOGD("Pointers: Ignoring driver provided pointer id %d because "
"it is larger than max supported id %d for optimizations",
id, MAX_POINTER_ID);
#endif
havePointerIds = false;
}
else {
out->pointers[outCount].id = id;
out->idToIndex[id] = outCount;
out->idBits.markBit(id);
}
} else {
havePointerIds = false;
}
}
outCount += 1;
}
out->pointerCount = outCount;
onTouchScreenChanged(when, device, havePointerIds);
}
void InputReader::onSingleTouchScreenStateChanged(nsecs_t when,
InputDevice* device) {
static const uint32_t POSITION_FIELDS =
InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_X
| InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_Y
| InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_PRESSURE
| InputDevice::SingleTouchScreenState::Accumulator::FIELD_ABS_TOOL_WIDTH;
/* Refresh display properties so we can map touch screen coords into display coords */
if (! refreshDisplayProperties()) {
return;
}
/* Update device state */
InputDevice::SingleTouchScreenState* in = & device->singleTouchScreen;
InputDevice::TouchData* out = & device->touchScreen.currentTouch;
uint32_t fields = in->accumulator.fields;
if (fields & InputDevice::SingleTouchScreenState::Accumulator::FIELD_BTN_TOUCH) {
in->current.down = in->accumulator.btnTouch;
}
if ((fields & POSITION_FIELDS) == POSITION_FIELDS) {
in->current.x = in->accumulator.absX;
in->current.y = in->accumulator.absY;
in->current.pressure = in->accumulator.absPressure;
in->current.size = in->accumulator.absToolWidth;
}
out->clear();
if (in->current.down) {
out->pointerCount = 1;
out->pointers[0].id = 0;
out->pointers[0].x = in->current.x;
out->pointers[0].y = in->current.y;
out->pointers[0].pressure = in->current.pressure;
out->pointers[0].size = in->current.size;
out->idToIndex[0] = 0;
out->idBits.markBit(0);
}
onTouchScreenChanged(when, device, true);
}
void InputReader::onTouchScreenChanged(nsecs_t when,
InputDevice* device, bool havePointerIds) {
/* Apply policy */
int32_t policyActions = mPolicy->interceptTouch(when);
uint32_t policyFlags = 0;
if (! applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) {
device->touchScreen.lastTouch.clear();
return; // event dropped
}
/* Preprocess pointer data */
if (device->touchScreen.parameters.useBadTouchFilter) {
if (device->touchScreen.applyBadTouchFilter()) {
havePointerIds = false;
}
}
if (device->touchScreen.parameters.useJumpyTouchFilter) {
if (device->touchScreen.applyJumpyTouchFilter()) {
havePointerIds = false;
}
}
if (! havePointerIds) {
device->touchScreen.calculatePointerIds();
}
InputDevice::TouchData temp;
InputDevice::TouchData* savedTouch;
if (device->touchScreen.parameters.useAveragingTouchFilter) {
temp.copyFrom(device->touchScreen.currentTouch);
savedTouch = & temp;
device->touchScreen.applyAveragingTouchFilter();
} else {
savedTouch = & device->touchScreen.currentTouch;
}
/* Process virtual keys or touches */
if (! consumeVirtualKeyTouches(when, device, policyFlags)) {
dispatchTouches(when, device, policyFlags);
}
// Copy current touch to last touch in preparation for the next cycle.
device->touchScreen.lastTouch.copyFrom(*savedTouch);
}
bool InputReader::consumeVirtualKeyTouches(nsecs_t when,
InputDevice* device, uint32_t policyFlags) {
if (device->touchScreen.currentVirtualKey.down) {
if (device->touchScreen.currentTouch.pointerCount == 0) {
// Pointer went up while virtual key was down. Send key up event.
device->touchScreen.currentVirtualKey.down = false;
#if DEBUG_VIRTUAL_KEYS
LOGD("VirtualKeys: Generating key up: keyCode=%d, scanCode=%d",
device->touchScreen.currentVirtualKey.keyCode,
device->touchScreen.currentVirtualKey.scanCode);
#endif
dispatchVirtualKey(when, device, policyFlags, KEY_EVENT_ACTION_UP,
KEY_EVENT_FLAG_FROM_SYSTEM | KEY_EVENT_FLAG_VIRTUAL_HARD_KEY);
return true; // consumed
}
int32_t x = device->touchScreen.currentTouch.pointers[0].x;
int32_t y = device->touchScreen.currentTouch.pointers[0].y;
if (device->touchScreen.isPointInsideDisplay(x, y)) {
// Pointer moved inside the display area. Send key cancellation.
device->touchScreen.currentVirtualKey.down = false;
#if DEBUG_VIRTUAL_KEYS
LOGD("VirtualKeys: Canceling key: keyCode=%d, scanCode=%d",
device->touchScreen.currentVirtualKey.keyCode,
device->touchScreen.currentVirtualKey.scanCode);
#endif
dispatchVirtualKey(when, device, policyFlags, KEY_EVENT_ACTION_UP,
KEY_EVENT_FLAG_FROM_SYSTEM | KEY_EVENT_FLAG_VIRTUAL_HARD_KEY
| KEY_EVENT_FLAG_CANCELED);
// Clear the last touch data so we will consider the pointer as having just been
// pressed down when generating subsequent motion events.
device->touchScreen.lastTouch.clear();
return false; // not consumed
}
} else if (device->touchScreen.currentTouch.pointerCount > 0
&& device->touchScreen.lastTouch.pointerCount == 0) {
int32_t x = device->touchScreen.currentTouch.pointers[0].x;
int32_t y = device->touchScreen.currentTouch.pointers[0].y;
for (size_t i = 0; i < device->touchScreen.virtualKeys.size(); i++) {
const InputDevice::VirtualKey& virtualKey = device->touchScreen.virtualKeys[i];
#if DEBUG_VIRTUAL_KEYS
LOGD("VirtualKeys: Hit test (%d, %d): keyCode=%d, scanCode=%d, "
"left=%d, top=%d, right=%d, bottom=%d",
x, y,
virtualKey.keyCode, virtualKey.scanCode,
virtualKey.hitLeft, virtualKey.hitTop,
virtualKey.hitRight, virtualKey.hitBottom);
#endif
if (virtualKey.isHit(x, y)) {
device->touchScreen.currentVirtualKey.down = true;
device->touchScreen.currentVirtualKey.downTime = when;
device->touchScreen.currentVirtualKey.keyCode = virtualKey.keyCode;
device->touchScreen.currentVirtualKey.scanCode = virtualKey.scanCode;
#if DEBUG_VIRTUAL_KEYS
LOGD("VirtualKeys: Generating key down: keyCode=%d, scanCode=%d",
device->touchScreen.currentVirtualKey.keyCode,
device->touchScreen.currentVirtualKey.scanCode);
#endif
dispatchVirtualKey(when, device, policyFlags, KEY_EVENT_ACTION_DOWN,
KEY_EVENT_FLAG_FROM_SYSTEM | KEY_EVENT_FLAG_VIRTUAL_HARD_KEY);
return true; // consumed
}
}
}
return false; // not consumed
}
void InputReader::dispatchVirtualKey(nsecs_t when,
InputDevice* device, uint32_t policyFlags,
int32_t keyEventAction, int32_t keyEventFlags) {
int32_t keyCode = device->touchScreen.currentVirtualKey.keyCode;
int32_t scanCode = device->touchScreen.currentVirtualKey.scanCode;
nsecs_t downTime = device->touchScreen.currentVirtualKey.downTime;
int32_t metaState = globalMetaState();
updateGlobalVirtualKeyState();
mPolicy->virtualKeyFeedback(when, device->id, keyEventAction, keyEventFlags,
keyCode, scanCode, metaState, downTime);
mDispatcher->notifyKey(when, device->id, INPUT_EVENT_NATURE_KEY, policyFlags,
keyEventAction, keyEventFlags, keyCode, scanCode, metaState, downTime);
}
void InputReader::dispatchTouches(nsecs_t when,
InputDevice* device, uint32_t policyFlags) {
uint32_t currentPointerCount = device->touchScreen.currentTouch.pointerCount;
uint32_t lastPointerCount = device->touchScreen.lastTouch.pointerCount;
if (currentPointerCount == 0 && lastPointerCount == 0) {
return; // nothing to do!
}
BitSet32 currentIdBits = device->touchScreen.currentTouch.idBits;
BitSet32 lastIdBits = device->touchScreen.lastTouch.idBits;
if (currentIdBits == lastIdBits) {
// No pointer id changes so this is a move event.
// The dispatcher takes care of batching moves so we don't have to deal with that here.
int32_t motionEventAction = MOTION_EVENT_ACTION_MOVE;
dispatchTouch(when, device, policyFlags, & device->touchScreen.currentTouch,
currentIdBits, motionEventAction);
} else {
// There may be pointers going up and pointers going down at the same time when pointer
// ids are reported by the device driver.
BitSet32 upIdBits(lastIdBits.value & ~ currentIdBits.value);
BitSet32 downIdBits(currentIdBits.value & ~ lastIdBits.value);
BitSet32 activeIdBits(lastIdBits.value);
while (! upIdBits.isEmpty()) {
uint32_t upId = upIdBits.firstMarkedBit();
upIdBits.clearBit(upId);
BitSet32 oldActiveIdBits = activeIdBits;
activeIdBits.clearBit(upId);
int32_t motionEventAction;
if (activeIdBits.isEmpty()) {
motionEventAction = MOTION_EVENT_ACTION_UP;
} else {
motionEventAction = MOTION_EVENT_ACTION_POINTER_UP
| (upId << MOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
}
dispatchTouch(when, device, policyFlags, & device->touchScreen.lastTouch,
oldActiveIdBits, motionEventAction);
}
while (! downIdBits.isEmpty()) {
uint32_t downId = downIdBits.firstMarkedBit();
downIdBits.clearBit(downId);
BitSet32 oldActiveIdBits = activeIdBits;
activeIdBits.markBit(downId);
int32_t motionEventAction;
if (oldActiveIdBits.isEmpty()) {
motionEventAction = MOTION_EVENT_ACTION_DOWN;
device->touchScreen.downTime = when;
} else {
motionEventAction = MOTION_EVENT_ACTION_POINTER_DOWN
| (downId << MOTION_EVENT_ACTION_POINTER_INDEX_SHIFT);
}
dispatchTouch(when, device, policyFlags, & device->touchScreen.currentTouch,
activeIdBits, motionEventAction);
}
}
}
void InputReader::dispatchTouch(nsecs_t when, InputDevice* device, uint32_t policyFlags,
InputDevice::TouchData* touch, BitSet32 idBits,
int32_t motionEventAction) {
int32_t orientedWidth, orientedHeight;
switch (mDisplayOrientation) {
case InputDispatchPolicyInterface::ROTATION_90:
case InputDispatchPolicyInterface::ROTATION_270:
orientedWidth = mDisplayHeight;
orientedHeight = mDisplayWidth;
break;
default:
orientedWidth = mDisplayWidth;
orientedHeight = mDisplayHeight;
break;
}
uint32_t pointerCount = 0;
int32_t pointerIds[MAX_POINTERS];
PointerCoords pointerCoords[MAX_POINTERS];
// Walk through the the active pointers and map touch screen coordinates (TouchData) into
// display coordinates (PointerCoords) and adjust for display orientation.
while (! idBits.isEmpty()) {
uint32_t id = idBits.firstMarkedBit();
idBits.clearBit(id);
uint32_t index = touch->idToIndex[id];
float x = (float(touch->pointers[index].x)
- device->touchScreen.parameters.xAxis.minValue)
* device->touchScreen.precalculated.xScale;
float y = (float(touch->pointers[index].y)
- device->touchScreen.parameters.yAxis.minValue)
* device->touchScreen.precalculated.yScale;
float pressure = (float(touch->pointers[index].pressure)
- device->touchScreen.parameters.pressureAxis.minValue)
* device->touchScreen.precalculated.pressureScale;
float size = (float(touch->pointers[index].size)
- device->touchScreen.parameters.sizeAxis.minValue)
* device->touchScreen.precalculated.sizeScale;
switch (mDisplayOrientation) {
case InputDispatchPolicyInterface::ROTATION_90: {
float xTemp = x;
x = y;
y = mDisplayHeight - xTemp;
break;
}
case InputDispatchPolicyInterface::ROTATION_180: {
x = mDisplayWidth - x;
y = mDisplayHeight - y;
break;
}
case InputDispatchPolicyInterface::ROTATION_270: {
float xTemp = x;
x = mDisplayWidth - y;
y = xTemp;
break;
}
}
pointerIds[pointerCount] = int32_t(id);
pointerCoords[pointerCount].x = x;
pointerCoords[pointerCount].y = y;
pointerCoords[pointerCount].pressure = pressure;
pointerCoords[pointerCount].size = size;
pointerCount += 1;
}
// Check edge flags by looking only at the first pointer since the flags are
// global to the event.
// XXX Maybe we should revise the edge flags API to work on a per-pointer basis.
int32_t motionEventEdgeFlags = 0;
if (motionEventAction == MOTION_EVENT_ACTION_DOWN) {
if (pointerCoords[0].x <= 0) {
motionEventEdgeFlags |= MOTION_EVENT_EDGE_FLAG_LEFT;
} else if (pointerCoords[0].x >= orientedWidth) {
motionEventEdgeFlags |= MOTION_EVENT_EDGE_FLAG_RIGHT;
}
if (pointerCoords[0].y <= 0) {
motionEventEdgeFlags |= MOTION_EVENT_EDGE_FLAG_TOP;
} else if (pointerCoords[0].y >= orientedHeight) {
motionEventEdgeFlags |= MOTION_EVENT_EDGE_FLAG_BOTTOM;
}
}
nsecs_t downTime = device->touchScreen.downTime;
mDispatcher->notifyMotion(when, device->id, INPUT_EVENT_NATURE_TOUCH, policyFlags,
motionEventAction, globalMetaState(), motionEventEdgeFlags,
pointerCount, pointerIds, pointerCoords,
0, 0, downTime);
}
void InputReader::onTrackballStateChanged(nsecs_t when,
InputDevice* device) {
static const uint32_t DELTA_FIELDS =
InputDevice::TrackballState::Accumulator::FIELD_REL_X
| InputDevice::TrackballState::Accumulator::FIELD_REL_Y;
/* Refresh display properties so we can trackball moves according to display orientation */
if (! refreshDisplayProperties()) {
return;
}
/* Update device state */
uint32_t fields = device->trackball.accumulator.fields;
bool downChanged = fields & InputDevice::TrackballState::Accumulator::FIELD_BTN_MOUSE;
bool deltaChanged = (fields & DELTA_FIELDS) == DELTA_FIELDS;
bool down;
if (downChanged) {
if (device->trackball.accumulator.btnMouse) {
device->trackball.current.down = true;
device->trackball.current.downTime = when;
down = true;
} else {
device->trackball.current.down = false;
down = false;
}
} else {
down = device->trackball.current.down;
}
/* Apply policy */
int32_t policyActions = mPolicy->interceptTrackball(when, downChanged, down, deltaChanged);
uint32_t policyFlags = 0;
if (! applyStandardInputDispatchPolicyActions(when, policyActions, & policyFlags)) {
return; // event dropped
}
/* Enqueue motion event for dispatch */
int32_t motionEventAction;
if (downChanged) {
motionEventAction = down ? MOTION_EVENT_ACTION_DOWN : MOTION_EVENT_ACTION_UP;
} else {
motionEventAction = MOTION_EVENT_ACTION_MOVE;
}
int32_t pointerId = 0;
PointerCoords pointerCoords;
pointerCoords.x = device->trackball.accumulator.relX
* device->trackball.precalculated.xScale;
pointerCoords.y = device->trackball.accumulator.relY
* device->trackball.precalculated.yScale;
pointerCoords.pressure = 1.0f; // XXX Consider making this 1.0f if down, 0 otherwise.
pointerCoords.size = 0;
float temp;
switch (mDisplayOrientation) {
case InputDispatchPolicyInterface::ROTATION_90:
temp = pointerCoords.x;
pointerCoords.x = pointerCoords.y;
pointerCoords.y = - temp;
break;
case InputDispatchPolicyInterface::ROTATION_180:
pointerCoords.x = - pointerCoords.x;
pointerCoords.y = - pointerCoords.y;
break;
case InputDispatchPolicyInterface::ROTATION_270:
temp = pointerCoords.x;
pointerCoords.x = - pointerCoords.y;
pointerCoords.y = temp;
break;
}
mDispatcher->notifyMotion(when, device->id, INPUT_EVENT_NATURE_TRACKBALL, policyFlags,
motionEventAction, globalMetaState(), MOTION_EVENT_EDGE_FLAG_NONE,
1, & pointerId, & pointerCoords,
device->trackball.precalculated.xPrecision,
device->trackball.precalculated.yPrecision,
device->trackball.current.downTime);
}
void InputReader::onConfigurationChanged(nsecs_t when) {
// Reset global meta state because it depends on the list of all configured devices.
resetGlobalMetaState();
// Reset virtual keys, just in case.
updateGlobalVirtualKeyState();
// Enqueue configuration changed.
// XXX This stuff probably needs to be tracked elsewhere in an input device registry
// of some kind that can be asynchronously updated and queried. (Same as above?)
int32_t touchScreenConfig = InputDispatchPolicyInterface::TOUCHSCREEN_NOTOUCH;
int32_t keyboardConfig = InputDispatchPolicyInterface::KEYBOARD_NOKEYS;
int32_t navigationConfig = InputDispatchPolicyInterface::NAVIGATION_NONAV;
for (size_t i = 0; i < mDevices.size(); i++) {
InputDevice* device = mDevices.valueAt(i);
int32_t deviceClasses = device->classes;
if (deviceClasses & INPUT_DEVICE_CLASS_TOUCHSCREEN) {
touchScreenConfig = InputDispatchPolicyInterface::TOUCHSCREEN_FINGER;
}
if (deviceClasses & INPUT_DEVICE_CLASS_ALPHAKEY) {
keyboardConfig = InputDispatchPolicyInterface::KEYBOARD_QWERTY;
}
if (deviceClasses & INPUT_DEVICE_CLASS_TRACKBALL) {
navigationConfig = InputDispatchPolicyInterface::NAVIGATION_TRACKBALL;
} else if (deviceClasses & INPUT_DEVICE_CLASS_DPAD) {
navigationConfig = InputDispatchPolicyInterface::NAVIGATION_DPAD;
}
}
mDispatcher->notifyConfigurationChanged(when, touchScreenConfig,
keyboardConfig, navigationConfig);
}
bool InputReader::applyStandardInputDispatchPolicyActions(nsecs_t when,
int32_t policyActions, uint32_t* policyFlags) {
if (policyActions & InputDispatchPolicyInterface::ACTION_APP_SWITCH_COMING) {
mDispatcher->notifyAppSwitchComing(when);
}
if (policyActions & InputDispatchPolicyInterface::ACTION_WOKE_HERE) {
*policyFlags |= POLICY_FLAG_WOKE_HERE;
}
if (policyActions & InputDispatchPolicyInterface::ACTION_BRIGHT_HERE) {
*policyFlags |= POLICY_FLAG_BRIGHT_HERE;
}
return policyActions & InputDispatchPolicyInterface::ACTION_DISPATCH;
}
void InputReader::resetDisplayProperties() {
mDisplayWidth = mDisplayHeight = -1;
mDisplayOrientation = -1;
}
bool InputReader::refreshDisplayProperties() {
int32_t newWidth, newHeight, newOrientation;
if (mPolicy->getDisplayInfo(0, & newWidth, & newHeight, & newOrientation)) {
if (newWidth != mDisplayWidth || newHeight != mDisplayHeight) {
LOGD("Display size changed from %dx%d to %dx%d, updating device configuration",
mDisplayWidth, mDisplayHeight, newWidth, newHeight);
mDisplayWidth = newWidth;
mDisplayHeight = newHeight;
for (size_t i = 0; i < mDevices.size(); i++) {
configureDeviceForCurrentDisplaySize(mDevices.valueAt(i));
}
}
mDisplayOrientation = newOrientation;
return true;
} else {
resetDisplayProperties();
return false;
}
}
InputDevice* InputReader::getDevice(int32_t deviceId) {
ssize_t index = mDevices.indexOfKey(deviceId);
return index >= 0 ? mDevices.valueAt((size_t) index) : NULL;
}
InputDevice* InputReader::getNonIgnoredDevice(int32_t deviceId) {
InputDevice* device = getDevice(deviceId);
return device && ! device->ignored ? device : NULL;
}
void InputReader::addDevice(nsecs_t when, int32_t deviceId) {
uint32_t classes = mEventHub->getDeviceClasses(deviceId);
String8 name = mEventHub->getDeviceName(deviceId);
InputDevice* device = new InputDevice(deviceId, classes, name);
if (classes != 0) {
LOGI("Device added: id=0x%x, name=%s, classes=%02x", device->id,
device->name.string(), device->classes);
configureDevice(device);
} else {
LOGI("Device added: id=0x%x, name=%s (ignored non-input device)", device->id,
device->name.string());
device->ignored = true;
}
device->reset();
mDevices.add(deviceId, device);
if (! device->ignored) {
onConfigurationChanged(when);
}
}
void InputReader::removeDevice(nsecs_t when, InputDevice* device) {
mDevices.removeItem(device->id);
if (! device->ignored) {
LOGI("Device removed: id=0x%x, name=%s, classes=%02x", device->id,
device->name.string(), device->classes);
onConfigurationChanged(when);
} else {
LOGI("Device removed: id=0x%x, name=%s (ignored non-input device)", device->id,
device->name.string());
}
delete device;
}
void InputReader::configureDevice(InputDevice* device) {
if (device->isMultiTouchScreen()) {
configureAbsoluteAxisInfo(device, ABS_MT_POSITION_X, "X",
& device->touchScreen.parameters.xAxis);
configureAbsoluteAxisInfo(device, ABS_MT_POSITION_Y, "Y",
& device->touchScreen.parameters.yAxis);
configureAbsoluteAxisInfo(device, ABS_MT_TOUCH_MAJOR, "Pressure",
& device->touchScreen.parameters.pressureAxis);
configureAbsoluteAxisInfo(device, ABS_MT_WIDTH_MAJOR, "Size",
& device->touchScreen.parameters.sizeAxis);
} else if (device->isSingleTouchScreen()) {
configureAbsoluteAxisInfo(device, ABS_X, "X",
& device->touchScreen.parameters.xAxis);
configureAbsoluteAxisInfo(device, ABS_Y, "Y",
& device->touchScreen.parameters.yAxis);
configureAbsoluteAxisInfo(device, ABS_PRESSURE, "Pressure",
& device->touchScreen.parameters.pressureAxis);
configureAbsoluteAxisInfo(device, ABS_TOOL_WIDTH, "Size",
& device->touchScreen.parameters.sizeAxis);
}
if (device->isTouchScreen()) {
device->touchScreen.parameters.useBadTouchFilter =
mPolicy->filterTouchEvents();
device->touchScreen.parameters.useAveragingTouchFilter =
mPolicy->filterTouchEvents();
device->touchScreen.parameters.useJumpyTouchFilter =
mPolicy->filterJumpyTouchEvents();
device->touchScreen.precalculated.pressureScale =
1.0f / device->touchScreen.parameters.pressureAxis.range;
device->touchScreen.precalculated.sizeScale =
1.0f / device->touchScreen.parameters.sizeAxis.range;
}
if (device->isTrackball()) {
device->trackball.precalculated.xPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
device->trackball.precalculated.yPrecision = TRACKBALL_MOVEMENT_THRESHOLD;
device->trackball.precalculated.xScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
device->trackball.precalculated.yScale = 1.0f / TRACKBALL_MOVEMENT_THRESHOLD;
}
configureDeviceForCurrentDisplaySize(device);
}
void InputReader::configureDeviceForCurrentDisplaySize(InputDevice* device) {
if (device->isTouchScreen()) {
if (mDisplayWidth < 0) {
LOGD("Skipping part of touch screen configuration since display size is unknown.");
} else {
LOGI("Device configured: id=0x%x, name=%s (display size was changed)", device->id,
device->name.string());
configureVirtualKeys(device);
device->touchScreen.precalculated.xScale =
float(mDisplayWidth) / device->touchScreen.parameters.xAxis.range;
device->touchScreen.precalculated.yScale =
float(mDisplayHeight) / device->touchScreen.parameters.yAxis.range;
}
}
}
void InputReader::configureVirtualKeys(InputDevice* device) {
device->touchScreen.virtualKeys.clear();
Vector<InputDispatchPolicyInterface::VirtualKeyDefinition> virtualKeyDefinitions;
mPolicy->getVirtualKeyDefinitions(device->name, virtualKeyDefinitions);
if (virtualKeyDefinitions.size() == 0) {
return;
}
device->touchScreen.virtualKeys.setCapacity(virtualKeyDefinitions.size());
int32_t touchScreenLeft = device->touchScreen.parameters.xAxis.minValue;
int32_t touchScreenTop = device->touchScreen.parameters.yAxis.minValue;
int32_t touchScreenWidth = device->touchScreen.parameters.xAxis.range;
int32_t touchScreenHeight = device->touchScreen.parameters.yAxis.range;
for (size_t i = 0; i < virtualKeyDefinitions.size(); i++) {
const InputDispatchPolicyInterface::VirtualKeyDefinition& virtualKeyDefinition =
virtualKeyDefinitions[i];
device->touchScreen.virtualKeys.add();
InputDevice::VirtualKey& virtualKey =
device->touchScreen.virtualKeys.editTop();
virtualKey.scanCode = virtualKeyDefinition.scanCode;
int32_t keyCode;
uint32_t flags;
if (mEventHub->scancodeToKeycode(device->id, virtualKey.scanCode,
& keyCode, & flags)) {
LOGI(" VirtualKey %d: could not obtain key code, ignoring", virtualKey.scanCode);
device->touchScreen.virtualKeys.pop(); // drop the key
continue;
}
virtualKey.keyCode = keyCode;
virtualKey.flags = flags;
// convert the key definition's display coordinates into touch coordinates for a hit box
int32_t halfWidth = virtualKeyDefinition.width / 2;
int32_t halfHeight = virtualKeyDefinition.height / 2;
virtualKey.hitLeft = (virtualKeyDefinition.centerX - halfWidth)
* touchScreenWidth / mDisplayWidth + touchScreenLeft;
virtualKey.hitRight= (virtualKeyDefinition.centerX + halfWidth)
* touchScreenWidth / mDisplayWidth + touchScreenLeft;
virtualKey.hitTop = (virtualKeyDefinition.centerY - halfHeight)
* touchScreenHeight / mDisplayHeight + touchScreenTop;
virtualKey.hitBottom = (virtualKeyDefinition.centerY + halfHeight)
* touchScreenHeight / mDisplayHeight + touchScreenTop;
LOGI(" VirtualKey %d: keyCode=%d hitLeft=%d hitRight=%d hitTop=%d hitBottom=%d",
virtualKey.scanCode, virtualKey.keyCode,
virtualKey.hitLeft, virtualKey.hitRight, virtualKey.hitTop, virtualKey.hitBottom);
}
}
void InputReader::configureAbsoluteAxisInfo(InputDevice* device,
int axis, const char* name, InputDevice::AbsoluteAxisInfo* out) {
if (! mEventHub->getAbsoluteInfo(device->id, axis,
& out->minValue, & out->maxValue, & out->flat, &out->fuzz)) {
out->range = out->maxValue - out->minValue;
if (out->range != 0) {
LOGI(" %s: min=%d max=%d flat=%d fuzz=%d",
name, out->minValue, out->maxValue, out->flat, out->fuzz);
return;
}
}
out->minValue = 0;
out->maxValue = 0;
out->flat = 0;
out->fuzz = 0;
out->range = 0;
LOGI(" %s: unknown axis values, setting to zero", name);
}
void InputReader::resetGlobalMetaState() {
mGlobalMetaState = -1;
}
int32_t InputReader::globalMetaState() {
if (mGlobalMetaState == -1) {
mGlobalMetaState = 0;
for (size_t i = 0; i < mDevices.size(); i++) {
InputDevice* device = mDevices.valueAt(i);
if (device->isKeyboard()) {
mGlobalMetaState |= device->keyboard.current.metaState;
}
}
}
return mGlobalMetaState;
}
void InputReader::updateGlobalVirtualKeyState() {
int32_t keyCode = -1, scanCode = -1;
for (size_t i = 0; i < mDevices.size(); i++) {
InputDevice* device = mDevices.valueAt(i);
if (device->isTouchScreen()) {
if (device->touchScreen.currentVirtualKey.down) {
keyCode = device->touchScreen.currentVirtualKey.keyCode;
scanCode = device->touchScreen.currentVirtualKey.scanCode;
}
}
}
{
AutoMutex _l(mExportedStateLock);
mGlobalVirtualKeyCode = keyCode;
mGlobalVirtualScanCode = scanCode;
}
}
bool InputReader::getCurrentVirtualKey(int32_t* outKeyCode, int32_t* outScanCode) const {
AutoMutex _l(mExportedStateLock);
*outKeyCode = mGlobalVirtualKeyCode;
*outScanCode = mGlobalVirtualScanCode;
return mGlobalVirtualKeyCode != -1;
}
// --- InputReaderThread ---
InputReaderThread::InputReaderThread(const sp<InputReaderInterface>& reader) :
Thread(/*canCallJava*/ true), mReader(reader) {
}
InputReaderThread::~InputReaderThread() {
}
bool InputReaderThread::threadLoop() {
mReader->loopOnce();
return true;
}
} // namespace android