From 9e2ad36be87f2703b3d737189944d82f93bd4f27 Mon Sep 17 00:00:00 2001 From: Jeff Brown Date: Fri, 30 Jul 2010 19:20:11 -0700 Subject: [PATCH] Enhanced VelocityTracker for > 5 pointers and fixed bugs. Improved PointerLocation tool to use VelocityTracker more efficiently and correctly when multiple pointers are down. Fixed a bug in TouchInputMapper where it was not correctly copying the id to index map in the last touch data. This could cause strange behavior on secondary pointer up events. Also added finished callback pooling in InputQueue. Change-Id: Ia85e52ac2fb7350960ea1d7edfbe81a1b3e8267b --- core/java/android/view/InputQueue.java | 48 +++- core/java/android/view/MotionEvent.java | 41 ++- core/java/android/view/VelocityTracker.java | 253 ++++++++++++------ .../internal/widget/PointerLocationView.java | 36 +-- include/ui/InputReader.h | 4 +- 5 files changed, 249 insertions(+), 133 deletions(-) diff --git a/core/java/android/view/InputQueue.java b/core/java/android/view/InputQueue.java index 997246f60235e..13d8104316122 100644 --- a/core/java/android/view/InputQueue.java +++ b/core/java/android/view/InputQueue.java @@ -35,7 +35,7 @@ public final class InputQueue { final InputChannel mChannel; - private static Object sLock = new Object(); + private static final Object sLock = new Object(); private static native void nativeRegisterInputChannel(InputChannel inputChannel, InputHandler inputHandler, MessageQueue messageQueue); @@ -103,28 +103,64 @@ public final class InputQueue { @SuppressWarnings("unused") private static void dispatchKeyEvent(InputHandler inputHandler, KeyEvent event, long finishedToken) { - Runnable finishedCallback = new FinishedCallback(finishedToken); + Runnable finishedCallback = FinishedCallback.obtain(finishedToken); inputHandler.handleKey(event, finishedCallback); } @SuppressWarnings("unused") private static void dispatchMotionEvent(InputHandler inputHandler, MotionEvent event, long finishedToken) { - Runnable finishedCallback = new FinishedCallback(finishedToken); + Runnable finishedCallback = FinishedCallback.obtain(finishedToken); inputHandler.handleMotion(event, finishedCallback); } - // TODO consider recycling finished callbacks when done private static class FinishedCallback implements Runnable { + private static final boolean DEBUG_RECYCLING = false; + + private static final int RECYCLE_MAX_COUNT = 4; + + private static FinishedCallback sRecycleHead; + private static int sRecycleCount; + + private FinishedCallback mRecycleNext; private long mFinishedToken; - public FinishedCallback(long finishedToken) { - mFinishedToken = finishedToken; + private FinishedCallback() { + } + + public static FinishedCallback obtain(long finishedToken) { + synchronized (sLock) { + FinishedCallback callback = sRecycleHead; + if (callback != null) { + callback.mRecycleNext = null; + sRecycleHead = callback.mRecycleNext; + sRecycleCount -= 1; + } else { + callback = new FinishedCallback(); + } + callback.mFinishedToken = finishedToken; + return callback; + } } public void run() { synchronized (sLock) { + if (mFinishedToken == -1) { + throw new IllegalStateException("Event finished callback already invoked."); + } + nativeFinished(mFinishedToken); + mFinishedToken = -1; + + if (sRecycleCount < RECYCLE_MAX_COUNT) { + mRecycleNext = sRecycleHead; + sRecycleHead = this; + sRecycleCount += 1; + + if (DEBUG_RECYCLING) { + Slog.d(TAG, "Recycled finished callbacks: " + sRecycleCount); + } + } } } } diff --git a/core/java/android/view/MotionEvent.java b/core/java/android/view/MotionEvent.java index 98fe73f891d44..47061c1710e93 100644 --- a/core/java/android/view/MotionEvent.java +++ b/core/java/android/view/MotionEvent.java @@ -177,72 +177,65 @@ public final class MotionEvent extends InputEvent implements Parcelable { */ public static final int EDGE_RIGHT = 0x00000008; - /** + /* * Offset for the sample's X coordinate. - * @hide */ static private final int SAMPLE_X = 0; - /** + /* * Offset for the sample's Y coordinate. - * @hide */ static private final int SAMPLE_Y = 1; - /** + /* * Offset for the sample's pressure. - * @hide */ static private final int SAMPLE_PRESSURE = 2; - /** + /* * Offset for the sample's size - * @hide */ static private final int SAMPLE_SIZE = 3; - /** + /* * Offset for the sample's touch major axis length. - * @hide */ static private final int SAMPLE_TOUCH_MAJOR = 4; - /** + /* * Offset for the sample's touch minor axis length. - * @hide */ static private final int SAMPLE_TOUCH_MINOR = 5; - /** + /* * Offset for the sample's tool major axis length. - * @hide */ static private final int SAMPLE_TOOL_MAJOR = 6; - /** + /* * Offset for the sample's tool minor axis length. - * @hide */ static private final int SAMPLE_TOOL_MINOR = 7; - /** + /* * Offset for the sample's orientation. - * @hide */ static private final int SAMPLE_ORIENTATION = 8; - /** + /* * Number of data items for each sample. - * @hide */ static private final int NUM_SAMPLE_DATA = 9; - /** - * Number of possible pointers. - * @hide + /* + * Minimum number of pointers for which to reserve space when allocating new + * motion events. This is explicitly not a bound on the maximum number of pointers. */ - static public final int BASE_AVAIL_POINTERS = 5; + static private final int BASE_AVAIL_POINTERS = 5; + /* + * Minimum number of samples for which to reserve space when allocating new motion events. + */ static private final int BASE_AVAIL_SAMPLES = 8; static private final int MAX_RECYCLED = 10; diff --git a/core/java/android/view/VelocityTracker.java b/core/java/android/view/VelocityTracker.java index e69b80721bab7..068e7b6efbc98 100644 --- a/core/java/android/view/VelocityTracker.java +++ b/core/java/android/view/VelocityTracker.java @@ -38,7 +38,7 @@ public final class VelocityTracker implements Poolable { static final boolean localLOGV = DEBUG || Config.LOGV; static final int NUM_PAST = 10; - static final int LONGEST_PAST_TIME = 200; + static final int MAX_AGE_MILLISECONDS = 200; static final VelocityTracker[] mPool = new VelocityTracker[1]; private static final Pool sPool = Pools.synchronizedPool( @@ -54,14 +54,22 @@ public final class VelocityTracker implements Poolable { public void onReleased(VelocityTracker element) { } }, 2)); - - final float mPastX[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST]; - final float mPastY[][] = new float[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST]; - final long mPastTime[][] = new long[MotionEvent.BASE_AVAIL_POINTERS][NUM_PAST]; - - float mYVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS]; - float mXVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS]; - int mLastTouch; + + private static final int INITIAL_POINTERS = 5; + + private static final class PointerData { + public int id; + public float xVelocity; + public float yVelocity; + + public final float[] pastX = new float[NUM_PAST]; + public final float[] pastY = new float[NUM_PAST]; + public final long[] pastTime = new long[NUM_PAST]; // uses Long.MIN_VALUE as a sentinel + } + + private PointerData[] mPointers = new PointerData[INITIAL_POINTERS]; + private int mNumPointers; + private int mLastTouchIndex; private VelocityTracker mNext; @@ -107,12 +115,8 @@ public final class VelocityTracker implements Poolable { * Reset the velocity tracker back to its initial state. */ public void clear() { - final long[][] pastTime = mPastTime; - for (int p = 0; p < MotionEvent.BASE_AVAIL_POINTERS; p++) { - for (int i = 0; i < NUM_PAST; i++) { - pastTime[p][i] = Long.MIN_VALUE; - } - } + mNumPointers = 0; + mLastTouchIndex = 0; } /** @@ -125,37 +129,74 @@ public final class VelocityTracker implements Poolable { * @param ev The MotionEvent you received and would like to track. */ public void addMovement(MotionEvent ev) { - final int N = ev.getHistorySize(); + final int historySize = ev.getHistorySize(); final int pointerCount = ev.getPointerCount(); - int touchIndex = (mLastTouch + 1) % NUM_PAST; - for (int i=0; i= 0) { + final PointerData pointerData = pointers[i]; + if (ev.findPointerIndex(pointerData.id) == -1) { + // Pointer went up. + // Shuffle pointers down to fill the hole. Place the old pointer data at + // the end so we can recycle it if more pointers are added later. + mNumPointers -= 1; + final int remaining = mNumPointers - i; + if (remaining != 0) { + System.arraycopy(pointers, i + 1, pointers, i, remaining); + pointers[mNumPointers] = pointerData; + } + } } - for (int p = 0; p < pointerCount; p++) { - int id = ev.getPointerId(p); - mPastX[id][touchIndex] = ev.getHistoricalX(p, i); - mPastY[id][touchIndex] = ev.getHistoricalY(p, i); - mPastTime[id][touchIndex] = ev.getHistoricalEventTime(i); + } + + for (int i = 0; i < pointerCount; i++){ + final int pointerId = ev.getPointerId(i); + PointerData pointerData = getPointerData(pointerId); + if (pointerData == null) { + // Pointer went down. + // Add a new entry. Write a sentinel at the end of the pastTime trace so we + // will be able to tell where the trace started. + final PointerData[] oldPointers = mPointers; + final int newPointerIndex = mNumPointers; + if (newPointerIndex < oldPointers.length) { + pointerData = oldPointers[newPointerIndex]; + if (pointerData == null) { + pointerData = new PointerData(); + oldPointers[newPointerIndex] = pointerData; + } + } else { + final PointerData[] newPointers = new PointerData[newPointerIndex * 2]; + System.arraycopy(oldPointers, 0, newPointers, 0, newPointerIndex); + mPointers = newPointers; + pointerData = new PointerData(); + newPointers[newPointerIndex] = pointerData; + } + pointerData.id = pointerId; + pointerData.pastTime[lastTouchIndex] = Long.MIN_VALUE; + mNumPointers += 1; } - - touchIndex = (touchIndex + 1) % NUM_PAST; + + final float[] pastX = pointerData.pastX; + final float[] pastY = pointerData.pastY; + final long[] pastTime = pointerData.pastTime; + + for (int j = 0; j < historySize; j++) { + final int touchIndex = (nextTouchIndex + j) % NUM_PAST; + pastX[touchIndex] = ev.getHistoricalX(i, j); + pastY[touchIndex] = ev.getHistoricalY(i, j); + pastTime[touchIndex] = ev.getHistoricalEventTime(j); + } + pastX[finalTouchIndex] = ev.getX(i); + pastY[finalTouchIndex] = ev.getY(i); + pastTime[finalTouchIndex] = ev.getEventTime(); } - - // During calculation any pointer values with a time of MIN_VALUE are treated - // as a break in input. Initialize all to MIN_VALUE for each new touch index. - for (int id = 0; id < MotionEvent.BASE_AVAIL_POINTERS; id++) { - mPastTime[id][touchIndex] = Long.MIN_VALUE; - } - final long time = ev.getEventTime(); - for (int p = 0; p < pointerCount; p++) { - int id = ev.getPointerId(p); - mPastX[id][touchIndex] = ev.getX(p); - mPastY[id][touchIndex] = ev.getY(p); - mPastTime[id][touchIndex] = time; - } - - mLastTouch = touchIndex; + + mLastTouchIndex = finalTouchIndex; } /** @@ -182,54 +223,80 @@ public final class VelocityTracker implements Poolable { * must be positive. */ public void computeCurrentVelocity(int units, float maxVelocity) { - for (int pos = 0; pos < MotionEvent.BASE_AVAIL_POINTERS; pos++) { - final float[] pastX = mPastX[pos]; - final float[] pastY = mPastY[pos]; - final long[] pastTime = mPastTime[pos]; - final int lastTouch = mLastTouch; + final int numPointers = mNumPointers; + final PointerData[] pointers = mPointers; + final int lastTouchIndex = mLastTouchIndex; - // find oldest acceptable time - int oldestTouch = lastTouch; - if (pastTime[lastTouch] != Long.MIN_VALUE) { // cleared ? - final float acceptableTime = pastTime[lastTouch] - LONGEST_PAST_TIME; - int nextOldestTouch = (NUM_PAST + oldestTouch - 1) % NUM_PAST; - while (pastTime[nextOldestTouch] >= acceptableTime && - nextOldestTouch != lastTouch) { - oldestTouch = nextOldestTouch; - nextOldestTouch = (NUM_PAST + oldestTouch - 1) % NUM_PAST; + for (int p = 0; p < numPointers; p++) { + final PointerData pointerData = pointers[p]; + final long[] pastTime = pointerData.pastTime; + + // Search backwards in time for oldest acceptable time. + // Stop at the beginning of the trace as indicated by the sentinel time Long.MIN_VALUE. + int oldestTouchIndex = lastTouchIndex; + int numTouches = 1; + final long minTime = pastTime[lastTouchIndex] - MAX_AGE_MILLISECONDS; + while (numTouches < NUM_PAST) { + final int nextOldestTouchIndex = (oldestTouchIndex + NUM_PAST - 1) % NUM_PAST; + final long nextOldestTime = pastTime[nextOldestTouchIndex]; + if (nextOldestTime < minTime) { // also handles end of trace sentinel + break; } + oldestTouchIndex = nextOldestTouchIndex; + numTouches += 1; } - + + // If we have a lot of samples, skip the last received sample since it is + // probably pretty noisy compared to the sum of all of the traces already acquired. + if (numTouches > 3) { + numTouches -= 1; + } + // Kind-of stupid. - final float oldestX = pastX[oldestTouch]; - final float oldestY = pastY[oldestTouch]; - final long oldestTime = pastTime[oldestTouch]; + final float[] pastX = pointerData.pastX; + final float[] pastY = pointerData.pastY; + + final float oldestX = pastX[oldestTouchIndex]; + final float oldestY = pastY[oldestTouchIndex]; + final long oldestTime = pastTime[oldestTouchIndex]; + float accumX = 0; float accumY = 0; - int N = (lastTouch - oldestTouch + NUM_PAST) % NUM_PAST + 1; - // Skip the last received event, since it is probably pretty noisy. - if (N > 3) N--; - - for (int i=1; i < N; i++) { - final int j = (oldestTouch + i) % NUM_PAST; - final int dur = (int)(pastTime[j] - oldestTime); - if (dur == 0) continue; - float dist = pastX[j] - oldestX; - float vel = (dist/dur) * units; // pixels/frame. - accumX = (accumX == 0) ? vel : (accumX + vel) * .5f; - dist = pastY[j] - oldestY; - vel = (dist/dur) * units; // pixels/frame. - accumY = (accumY == 0) ? vel : (accumY + vel) * .5f; + for (int i = 1; i < numTouches; i++) { + final int touchIndex = (oldestTouchIndex + i) % NUM_PAST; + final int duration = (int)(pastTime[touchIndex] - oldestTime); + + if (duration == 0) continue; + + float delta = pastX[touchIndex] - oldestX; + float velocity = (delta / duration) * units; // pixels/frame. + accumX = (accumX == 0) ? velocity : (accumX + velocity) * .5f; + + delta = pastY[touchIndex] - oldestY; + velocity = (delta / duration) * units; // pixels/frame. + accumY = (accumY == 0) ? velocity : (accumY + velocity) * .5f; } - mXVelocity[pos] = accumX < 0.0f ? Math.max(accumX, -maxVelocity) - : Math.min(accumX, maxVelocity); - mYVelocity[pos] = accumY < 0.0f ? Math.max(accumY, -maxVelocity) - : Math.min(accumY, maxVelocity); - - if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity=" - + mXVelocity + " N=" + N); + if (accumX < -maxVelocity) { + accumX = - maxVelocity; + } else if (accumX > maxVelocity) { + accumX = maxVelocity; + } + + if (accumY < -maxVelocity) { + accumY = - maxVelocity; + } else if (accumY > maxVelocity) { + accumY = maxVelocity; + } + + pointerData.xVelocity = accumX; + pointerData.yVelocity = accumY; + + if (localLOGV) { + Log.v(TAG, "[" + p + "] Pointer " + pointerData.id + + ": Y velocity=" + accumX +" X velocity=" + accumY + " N=" + numTouches); + } } } @@ -240,7 +307,8 @@ public final class VelocityTracker implements Poolable { * @return The previously computed X velocity. */ public float getXVelocity() { - return mXVelocity[0]; + PointerData pointerData = getPointerData(0); + return pointerData != null ? pointerData.xVelocity : 0; } /** @@ -250,7 +318,8 @@ public final class VelocityTracker implements Poolable { * @return The previously computed Y velocity. */ public float getYVelocity() { - return mYVelocity[0]; + PointerData pointerData = getPointerData(0); + return pointerData != null ? pointerData.yVelocity : 0; } /** @@ -261,7 +330,8 @@ public final class VelocityTracker implements Poolable { * @return The previously computed X velocity. */ public float getXVelocity(int id) { - return mXVelocity[id]; + PointerData pointerData = getPointerData(id); + return pointerData != null ? pointerData.xVelocity : 0; } /** @@ -272,6 +342,19 @@ public final class VelocityTracker implements Poolable { * @return The previously computed Y velocity. */ public float getYVelocity(int id) { - return mYVelocity[id]; + PointerData pointerData = getPointerData(id); + return pointerData != null ? pointerData.yVelocity : 0; + } + + private final PointerData getPointerData(int id) { + final PointerData[] pointers = mPointers; + final int numPointers = mNumPointers; + for (int p = 0; p < numPointers; p++) { + PointerData pointerData = pointers[p]; + if (pointerData.id == id) { + return pointerData; + } + } + return null; } } diff --git a/core/java/com/android/internal/widget/PointerLocationView.java b/core/java/com/android/internal/widget/PointerLocationView.java index f487a1637dbc6..d5a99798fa185 100644 --- a/core/java/com/android/internal/widget/PointerLocationView.java +++ b/core/java/com/android/internal/widget/PointerLocationView.java @@ -38,7 +38,8 @@ public class PointerLocationView extends View { private float mCurPressure; private float mCurSize; private int mCurWidth; - private VelocityTracker mVelocity; + private float mXVelocity; + private float mYVelocity; } private final ViewConfiguration mVC; @@ -56,6 +57,8 @@ public class PointerLocationView extends View { private final ArrayList mPointers = new ArrayList(); + private final VelocityTracker mVelocity; + private boolean mPrintCoords = true; public PointerLocationView(Context c) { @@ -88,8 +91,9 @@ public class PointerLocationView extends View { mPaint.setStrokeWidth(1); PointerState ps = new PointerState(); - ps.mVelocity = VelocityTracker.obtain(); mPointers.add(ps); + + mVelocity = VelocityTracker.obtain(); } public void setPrintCoords(boolean state) { @@ -146,11 +150,11 @@ public class PointerLocationView extends View { } canvas.drawRect(itemW * 3, 0, (itemW * 4) - 1, bottom, mTextBackgroundPaint); - int velocity = ps.mVelocity == null ? 0 : (int) (ps.mVelocity.getXVelocity() * 1000); + int velocity = (int) (ps.mXVelocity * 1000); canvas.drawText("Xv: " + velocity, 1 + itemW * 3, base, mTextPaint); canvas.drawRect(itemW * 4, 0, (itemW * 5) - 1, bottom, mTextBackgroundPaint); - velocity = ps.mVelocity == null ? 0 : (int) (ps.mVelocity.getYVelocity() * 1000); + velocity = (int) (ps.mYVelocity * 1000); canvas.drawText("Yv: " + velocity, 1 + itemW * 4, base, mTextPaint); canvas.drawRect(itemW * 5, 0, (itemW * 6) - 1, bottom, mTextBackgroundPaint); @@ -205,14 +209,10 @@ public class PointerLocationView extends View { } if (drawn) { - if (ps.mVelocity != null) { - mPaint.setARGB(255, 255, 64, 128); - float xVel = ps.mVelocity.getXVelocity() * (1000/60); - float yVel = ps.mVelocity.getYVelocity() * (1000/60); - canvas.drawLine(lastX, lastY, lastX+xVel, lastY+yVel, mPaint); - } else { - canvas.drawPoint(lastX, lastY, mPaint); - } + mPaint.setARGB(255, 255, 64, 128); + float xVel = ps.mXVelocity * (1000/60); + float yVel = ps.mYVelocity * (1000/60); + canvas.drawLine(lastX, lastY, lastX+xVel, lastY+yVel, mPaint); } } } @@ -236,11 +236,12 @@ public class PointerLocationView extends View { // mRect.setEmpty(); //} if (action == MotionEvent.ACTION_DOWN) { + mVelocity.clear(); + for (int p=0; p