Work on issue #2144454: Inconsistent swipes...
This introduces some hacks in the framework to try to clean up the
data we are getting from the touch screen. There are two main things
being done here:
1. Look for changes in position that are unreasonably large, and
ignore them. This is intended to eliminate the spurious jumps that
often happen when releasing.
2. Add some simple adaptive averaging of the touch data. If the
difference between the last and next point is large enough, we
disable the averaging; otherwise we average up to the last 5 points.
The goal is to get rid of the noise of small movements so that things
like taps don't look like short flings, while still responding quickly
to rapid movement.
For averaging pressure, we also weight each averaged coordinate by
the reported pressure at that point. This is intended to keep the
coordinates closer together during a release, when the pressure is
going down and the accuracy decreasing. It may also result in some
other interesting artifacts, but hopefully nothing problematic.
Change-Id: I1369e9ab015c406946a45c2d72547da9c604178f
diff --git a/services/java/com/android/server/InputDevice.java b/services/java/com/android/server/InputDevice.java
index e1bce73..2dc45b5 100644
--- a/services/java/com/android/server/InputDevice.java
+++ b/services/java/com/android/server/InputDevice.java
@@ -24,6 +24,7 @@
public class InputDevice {
static final boolean DEBUG_POINTERS = false;
+ static final boolean DEBUG_HACKS = false;
/** Amount that trackball needs to move in order to generate a key event. */
static final int TRACKBALL_MOVEMENT_THRESHOLD = 6;
@@ -76,6 +77,19 @@
final int[] mNextData = new int[(MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS)
+ MotionEvent.NUM_SAMPLE_DATA];
+ // Used to determine whether we dropped bad data, to avoid doing
+ // it repeatedly.
+ final boolean[] mDroppedBadPoint = new boolean[MAX_POINTERS];
+
+ // Used to perform averaging of reported coordinates, to smooth
+ // the data and filter out transients during a release.
+ static final int HISTORY_SIZE = 5;
+ int[] mHistoryDataStart = new int[MAX_POINTERS];
+ int[] mHistoryDataEnd = new int[MAX_POINTERS];
+ final int[] mHistoryData = new int[(MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS)
+ * HISTORY_SIZE];
+ final int[] mAveragedData = new int[MotionEvent.NUM_SAMPLE_DATA * MAX_POINTERS];
+
// Temporary data structures for doing the pointer ID mapping.
final int[] mLast2Next = new int[MAX_POINTERS];
final int[] mNext2Last = new int[MAX_POINTERS];
@@ -98,6 +112,183 @@
}
}
+ /**
+ * 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.
+ */
+ void dropBadPoint(InputDevice dev) {
+ // We should always have absY, but let's be paranoid.
+ if (dev.absY == null) {
+ return;
+ }
+ // 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 (mNextNumPointers != mLastNumPointers) {
+ return;
+ }
+
+ // 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.
+ final int maxDy = ((dev.absY.maxValue-dev.absY.minValue)*7)/16;
+
+ // Look through all new points and see if any are farther than
+ // acceptable from all previous points.
+ for (int i=mNextNumPointers-1; i>=0; i--) {
+ final int ioff = i * MotionEvent.NUM_SAMPLE_DATA;
+ //final int x = mNextData[ioff + MotionEvent.SAMPLE_X];
+ final int y = mNextData[ioff + MotionEvent.SAMPLE_Y];
+ if (DEBUG_HACKS) Log.v("InputDevice", "Looking at next point #" + i + ": y=" + y);
+ boolean dropped = false;
+ if (!mDroppedBadPoint[i] && mLastNumPointers > 0) {
+ dropped = true;
+ int closestDy = -1;
+ int closestY = -1;
+ // We will drop this new point if it is sufficiently
+ // far away from -all- last points.
+ for (int j=mLastNumPointers-1; j>=0; j--) {
+ final int joff = j * MotionEvent.NUM_SAMPLE_DATA;
+ //int dx = x - mLastData[joff + MotionEvent.SAMPLE_X];
+ int dy = y - mLastData[joff + MotionEvent.SAMPLE_Y];
+ //if (dx < 0) dx = -dx;
+ if (dy < 0) dy = -dy;
+ if (DEBUG_HACKS) Log.v("InputDevice", "Comparing with last point #" + j
+ + ": y=" + mLastData[joff] + " dy=" + dy);
+ if (dy < maxDy) {
+ dropped = false;
+ break;
+ } else if (closestDy < 0 || dy < closestDy) {
+ closestDy = dy;
+ closestY = mLastData[joff + MotionEvent.SAMPLE_Y];
+ }
+ }
+ if (dropped) {
+ dropped = true;
+ Log.i("InputDevice", "Dropping bad point #" + i
+ + ": newY=" + y + " closestDy=" + closestDy
+ + " maxDy=" + maxDy);
+ mNextData[ioff + MotionEvent.SAMPLE_Y] = closestY;
+ break;
+ }
+ }
+ mDroppedBadPoint[i] = dropped;
+ }
+ }
+
+ /**
+ * 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.
+ */
+ int[] generateAveragedData(int upOrDownPointer, int lastNumPointers,
+ int nextNumPointers) {
+ final int numPointers = mLastNumPointers;
+ final int[] rawData = mLastData;
+ if (DEBUG_HACKS) Log.v("InputDevice", "lastNumPointers=" + lastNumPointers
+ + " nextNumPointers=" + nextNumPointers
+ + " numPointers=" + numPointers);
+ for (int i=0; i<numPointers; i++) {
+ final int ioff = i * MotionEvent.NUM_SAMPLE_DATA;
+ // We keep the average data in offsets based on the pointer
+ // ID, so we don't need to move it around as fingers are
+ // pressed and released.
+ final int p = mPointerIds[i];
+ final int poff = p * MotionEvent.NUM_SAMPLE_DATA * HISTORY_SIZE;
+ if (i == upOrDownPointer && lastNumPointers != nextNumPointers) {
+ if (lastNumPointers < nextNumPointers) {
+ // This pointer is going down. Clear its history
+ // and start fresh.
+ if (DEBUG_HACKS) Log.v("InputDevice", "Pointer down @ index "
+ + upOrDownPointer + " id " + mPointerIds[i]);
+ mHistoryDataStart[i] = 0;
+ mHistoryDataEnd[i] = 0;
+ System.arraycopy(rawData, ioff, mHistoryData, poff,
+ MotionEvent.NUM_SAMPLE_DATA);
+ System.arraycopy(rawData, ioff, mAveragedData, ioff,
+ MotionEvent.NUM_SAMPLE_DATA);
+ continue;
+ } else {
+ // The pointer is going up. Just fall through to
+ // recompute the last averaged point (and don't add
+ // it as a new point to include in the average).
+ if (DEBUG_HACKS) Log.v("InputDevice", "Pointer up @ index "
+ + upOrDownPointer + " id " + mPointerIds[i]);
+ }
+ } else {
+ int end = mHistoryDataEnd[i];
+ int eoff = poff + (end*MotionEvent.NUM_SAMPLE_DATA);
+ int oldX = mHistoryData[eoff + MotionEvent.SAMPLE_X];
+ int oldY = mHistoryData[eoff + MotionEvent.SAMPLE_Y];
+ int newX = rawData[ioff + MotionEvent.SAMPLE_X];
+ int newY = rawData[ioff + MotionEvent.SAMPLE_Y];
+ int dx = newX-oldX;
+ int dy = newY-oldY;
+ int delta = dx*dx + dy*dy;
+ if (DEBUG_HACKS) Log.v("InputDevice", "Delta from last: " + delta);
+ if (delta >= (75*75)) {
+ // Magic number, if moving farther than this, turn
+ // off filtering to avoid lag in response.
+ mHistoryDataStart[i] = 0;
+ mHistoryDataEnd[i] = 0;
+ System.arraycopy(rawData, ioff, mHistoryData, poff,
+ MotionEvent.NUM_SAMPLE_DATA);
+ System.arraycopy(rawData, ioff, mAveragedData, ioff,
+ MotionEvent.NUM_SAMPLE_DATA);
+ continue;
+ } else {
+ end++;
+ if (end >= HISTORY_SIZE) {
+ end -= HISTORY_SIZE;
+ }
+ mHistoryDataEnd[i] = end;
+ int noff = poff + (end*MotionEvent.NUM_SAMPLE_DATA);
+ mHistoryData[noff + MotionEvent.SAMPLE_X] = newX;
+ mHistoryData[noff + MotionEvent.SAMPLE_Y] = newY;
+ mHistoryData[noff + MotionEvent.SAMPLE_PRESSURE]
+ = rawData[ioff + MotionEvent.SAMPLE_PRESSURE];
+ int start = mHistoryDataStart[i];
+ if (end == start) {
+ start++;
+ if (start >= HISTORY_SIZE) {
+ start -= HISTORY_SIZE;
+ }
+ mHistoryDataStart[i] = start;
+ }
+ }
+ }
+
+ // Now compute the average.
+ int start = mHistoryDataStart[i];
+ int end = mHistoryDataEnd[i];
+ int x=0, y=0;
+ int totalPressure = 0;
+ while (start != end) {
+ int soff = poff + (start*MotionEvent.NUM_SAMPLE_DATA);
+ int pressure = mHistoryData[soff + MotionEvent.SAMPLE_PRESSURE];
+ x += mHistoryData[soff + MotionEvent.SAMPLE_X] * pressure;
+ y += mHistoryData[soff + MotionEvent.SAMPLE_Y] * pressure;
+ totalPressure += pressure;
+ start++;
+ if (start >= HISTORY_SIZE) start = 0;
+ }
+ int eoff = poff + (end*MotionEvent.NUM_SAMPLE_DATA);
+ int pressure = mHistoryData[eoff + MotionEvent.SAMPLE_PRESSURE];
+ x += mHistoryData[eoff + MotionEvent.SAMPLE_X] * pressure;
+ y += mHistoryData[eoff + MotionEvent.SAMPLE_Y] * pressure;
+ totalPressure += pressure;
+ x /= totalPressure;
+ y /= totalPressure;
+ if (DEBUG_HACKS) Log.v("InputDevice", "Averaging " + totalPressure
+ + " weight: (" + x + "," + y + ")");
+ mAveragedData[ioff + MotionEvent.SAMPLE_X] = x;
+ mAveragedData[ioff + MotionEvent.SAMPLE_Y] = y;
+ }
+ return mAveragedData;
+ }
+
private boolean assignPointer(int nextIndex, boolean allowOverlap) {
final int lastNumPointers = mLastNumPointers;
final int[] next2Last = mNext2Last;
@@ -333,7 +524,13 @@
int upOrDownPointer = updatePointerIdentifiers();
final float[] reportData = mReportData;
- final int[] rawData = mLastData;
+ final int[] rawData;
+ if (KeyInputQueue.BAD_TOUCH_HACK) {
+ rawData = generateAveragedData(upOrDownPointer, lastNumPointers,
+ nextNumPointers);
+ } else {
+ rawData = mLastData;
+ }
final int numPointers = mLastNumPointers;
diff --git a/services/java/com/android/server/KeyInputQueue.java b/services/java/com/android/server/KeyInputQueue.java
index 35ed448..09591f4 100644
--- a/services/java/com/android/server/KeyInputQueue.java
+++ b/services/java/com/android/server/KeyInputQueue.java
@@ -52,6 +52,12 @@
static final boolean DEBUG_VIRTUAL_KEYS = false;
static final boolean DEBUG_POINTERS = false;
+ /**
+ * Turn on some hacks we have to improve the touch interaction with a
+ * certain device whose screen currently is not all that good.
+ */
+ static final boolean BAD_TOUCH_HACK = true;
+
private static final String EXCLUDED_DEVICES_PATH = "etc/excluded-input-devices.xml";
final SparseArray<InputDevice> mDevices = new SparseArray<InputDevice>();
@@ -540,19 +546,17 @@
keycode, 0, scancode,
((ev.flags & WindowManagerPolicy.FLAG_WOKE_HERE) != 0)
? KeyEvent.FLAG_WOKE_HERE : 0));
+
} else if (ev.type == RawInputEvent.EV_KEY) {
+ // Single touch protocol: touch going down or up.
if (ev.scancode == RawInputEvent.BTN_TOUCH &&
(classes&(RawInputEvent.CLASS_TOUCHSCREEN
|RawInputEvent.CLASS_TOUCHSCREEN_MT))
== RawInputEvent.CLASS_TOUCHSCREEN) {
di.mAbs.changed = true;
di.mAbs.mDown[0] = ev.value != 0;
- } else if (ev.scancode == RawInputEvent.BTN_2 &&
- (classes&(RawInputEvent.CLASS_TOUCHSCREEN
- |RawInputEvent.CLASS_TOUCHSCREEN_MT))
- == RawInputEvent.CLASS_TOUCHSCREEN) {
- di.mAbs.changed = true;
- di.mAbs.mDown[1] = ev.value != 0;
+
+ // Trackball (mouse) protocol: press down or up.
} else if (ev.scancode == RawInputEvent.BTN_MOUSE &&
(classes&RawInputEvent.CLASS_TRACKBALL) != 0) {
di.mRel.changed = true;
@@ -560,6 +564,7 @@
send = true;
}
+ // Process position events from multitouch protocol.
} else if (ev.type == RawInputEvent.EV_ABS &&
(classes&RawInputEvent.CLASS_TOUCHSCREEN_MT) != 0) {
if (ev.scancode == RawInputEvent.ABS_MT_TOUCH_MAJOR) {
@@ -585,10 +590,10 @@
di.mAbs.mNextData[di.mAbs.mAddingPointerOffset
+ MotionEvent.SAMPLE_SIZE] = ev.value;
}
-
+
+ // Process position events from single touch protocol.
} else if (ev.type == RawInputEvent.EV_ABS &&
(classes&RawInputEvent.CLASS_TOUCHSCREEN) != 0) {
- // Finger 1
if (ev.scancode == RawInputEvent.ABS_X) {
di.mAbs.changed = true;
di.curTouchVals[MotionEvent.SAMPLE_X] = ev.value;
@@ -605,18 +610,9 @@
di.curTouchVals[MotionEvent.SAMPLE_SIZE] = ev.value;
di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA
+ MotionEvent.SAMPLE_SIZE] = ev.value;
-
- // Finger 2
- } else if (ev.scancode == RawInputEvent.ABS_HAT0X) {
- di.mAbs.changed = true;
- di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA
- + MotionEvent.SAMPLE_X] = ev.value;
- } else if (ev.scancode == RawInputEvent.ABS_HAT0Y) {
- di.mAbs.changed = true;
- di.curTouchVals[MotionEvent.NUM_SAMPLE_DATA
- + MotionEvent.SAMPLE_Y] = ev.value;
}
+ // Process movement events from trackball (mouse) protocol.
} else if (ev.type == RawInputEvent.EV_REL &&
(classes&RawInputEvent.CLASS_TRACKBALL) != 0) {
// Add this relative movement into our totals.
@@ -629,6 +625,9 @@
}
}
+ // Handle multitouch protocol sync: tells us that the
+ // driver has returned all data for -one- of the pointers
+ // that is currently down.
if (ev.type == RawInputEvent.EV_SYN
&& ev.scancode == RawInputEvent.SYN_MT_REPORT
&& di.mAbs != null) {
@@ -654,6 +653,9 @@
if (DEBUG_POINTERS) Log.v(TAG, "MT_REPORT: no pointer");
}
}
+
+ // Handle general event sync: all data for the current
+ // event update has been delivered.
} else if (send || (ev.type == RawInputEvent.EV_SYN
&& ev.scancode == RawInputEvent.SYN_REPORT)) {
if (mDisplay != null) {
@@ -677,15 +679,10 @@
MotionEvent.NUM_SAMPLE_DATA);
ms.mNextNumPointers++;
}
- if (ms.mDown[1]) {
- System.arraycopy(di.curTouchVals,
- MotionEvent.NUM_SAMPLE_DATA,
- ms.mNextData,
- ms.mNextNumPointers
- * MotionEvent.NUM_SAMPLE_DATA,
- MotionEvent.NUM_SAMPLE_DATA);
- ms.mNextNumPointers++;
- }
+ }
+
+ if (BAD_TOUCH_HACK) {
+ ms.dropBadPoint(di);
}
boolean doMotion = !monitorVirtualKey(di,
@@ -719,6 +716,16 @@
RawInputEvent.CLASS_TOUCHSCREEN, me);
}
} while (ms.hasMore());
+ } else {
+ // We are consuming movement in the
+ // virtual key area... but still
+ // propagate this to the previous
+ // data for comparisons.
+ System.arraycopy(ms.mNextData, 0,
+ ms.mLastData, 0,
+ ms.mNextNumPointers
+ * MotionEvent.NUM_SAMPLE_DATA);
+ ms.mLastNumPointers = ms.mNextNumPointers;
}
ms.finish();