Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9e2ad36b authored by Jeff Brown's avatar Jeff Brown
Browse files

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
parent 543f250d
Loading
Loading
Loading
Loading
+42 −6
Original line number Diff line number Diff line
@@ -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);
                    }
                }
            }
        }
    }
+17 −24
Original line number Diff line number Diff line
@@ -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;
+168 −85
Original line number Diff line number Diff line
@@ -38,7 +38,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
    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<VelocityTracker> sPool = Pools.synchronizedPool(
@@ -55,13 +55,21 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
                }
            }, 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];
    private static final int INITIAL_POINTERS = 5;
    
    float mYVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS];
    float mXVelocity[] = new float[MotionEvent.BASE_AVAIL_POINTERS];
    int mLastTouch;
    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<VelocityTracker> {
     * 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<VelocityTracker> {
     * @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<N; i++) {
            for (int id = 0; id < MotionEvent.BASE_AVAIL_POINTERS; id++) {
                mPastTime[id][touchIndex] = Long.MIN_VALUE;
        final int lastTouchIndex = mLastTouchIndex;
        final int nextTouchIndex = (lastTouchIndex + 1) % NUM_PAST;
        final int finalTouchIndex = (nextTouchIndex + historySize) % NUM_PAST;
        
        if (pointerCount < mNumPointers) {
            final PointerData[] pointers = mPointers;
            int i = mNumPointers;
            while (--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);
        }
        
            touchIndex = (touchIndex + 1) % NUM_PAST;
        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;
            }
            
            final float[] pastX = pointerData.pastX;
            final float[] pastY = pointerData.pastY;
            final long[] pastTime = pointerData.pastTime;
            
        // 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;
            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);
            }
        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;
            pastX[finalTouchIndex] = ev.getX(i);
            pastY[finalTouchIndex] = ev.getY(i);
            pastTime[finalTouchIndex] = ev.getEventTime();
        }
        
        mLastTouch = touchIndex;
        mLastTouchIndex = finalTouchIndex;
    }

    /**
@@ -182,54 +223,80 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
     * 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;
        
            // 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;
        final int numPointers = mNumPointers;
        final PointerData[] pointers = mPointers;
        final int lastTouchIndex = mLastTouchIndex;
        
        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;
            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;
            
                dist = pastY[j] - oldestY;
                vel = (dist/dur) * units;   // pixels/frame.
                accumY = (accumY == 0) ? vel : (accumY + vel) * .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 (accumX < -maxVelocity) {
                accumX = - maxVelocity;
            } else if (accumX > maxVelocity) {
                accumX = maxVelocity;
            }
            
            if (localLOGV) Log.v(TAG, "Y velocity=" + mYVelocity +" X velocity="
                    + mXVelocity + " N=" + N);
            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<VelocityTracker> {
     * @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<VelocityTracker> {
     * @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<VelocityTracker> {
     * @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<VelocityTracker> {
     * @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;
    }
}
+19 −17
Original line number Diff line number Diff line
@@ -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<PointerState> mPointers
             = new ArrayList<PointerState>();
    
    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);
                    float xVel = ps.mXVelocity * (1000/60);
                    float yVel = ps.mYVelocity * (1000/60);
                    canvas.drawLine(lastX, lastY, lastX+xVel, lastY+yVel, mPaint);
                    } else {
                        canvas.drawPoint(lastX, lastY, mPaint);
                    }
                }
            }
        }
@@ -236,11 +236,12 @@ public class PointerLocationView extends View {
            //    mRect.setEmpty();
            //}
            if (action == MotionEvent.ACTION_DOWN) {
                mVelocity.clear();
                
                for (int p=0; p<NP; p++) {
                    final PointerState ps = mPointers.get(p);
                    ps.mXs.clear();
                    ps.mYs.clear();
                    ps.mVelocity = VelocityTracker.obtain();
                    ps.mCurDown = false;
                }
                mPointers.get(0).mCurDown = true;
@@ -256,12 +257,10 @@ public class PointerLocationView extends View {
                final int id = event.getPointerId(index);
                while (NP <= id) {
                    PointerState ps = new PointerState();
                    ps.mVelocity = VelocityTracker.obtain();
                    mPointers.add(ps);
                    NP++;
                }
                final PointerState ps = mPointers.get(id);
                ps.mVelocity = VelocityTracker.obtain();
                ps.mCurDown = true;
                if (mPrintCoords) {
                    Log.i("Pointer", "Pointer " + (id+1) + ": DOWN");
@@ -277,11 +276,12 @@ public class PointerLocationView extends View {
                mMaxNumPointers = mCurNumPointers;
            }

            mVelocity.addMovement(event);
            mVelocity.computeCurrentVelocity(1);
            
            for (int i=0; i<NI; i++) {
                final int id = event.getPointerId(i);
                final PointerState ps = mPointers.get(id);
                ps.mVelocity.addMovement(event);
                ps.mVelocity.computeCurrentVelocity(1);
                final int N = event.getHistorySize();
                for (int j=0; j<N; j++) {
                    if (mPrintCoords) {
@@ -309,6 +309,8 @@ public class PointerLocationView extends View {
                ps.mCurPressure = event.getPressure(i);
                ps.mCurSize = event.getSize(i);
                ps.mCurWidth = (int)(ps.mCurSize*(getWidth()/3));
                ps.mXVelocity = mVelocity.getXVelocity(id);
                ps.mYVelocity = mVelocity.getYVelocity(id);
            }
            
            if ((action&MotionEvent.ACTION_MASK) == MotionEvent.ACTION_POINTER_UP) {
+3 −1
Original line number Diff line number Diff line
@@ -565,7 +565,9 @@ protected:

            for (uint32_t i = 0; i < pointerCount; i++) {
                pointers[i] = other.pointers[i];
                idToIndex[i] = other.idToIndex[i];

                int id = pointers[i].id;
                idToIndex[id] = other.idToIndex[id];
            }
        }