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

Commit 7d0fb570 authored by Jeff Brown's avatar Jeff Brown Committed by Android Git Automerger
Browse files

am 16330e24: am 94e838f6: Merge "Improve VelocityTracker numerical stability....

am 16330e24: am 94e838f6: Merge "Improve VelocityTracker numerical stability. (DO NOT MERGE)" into honeycomb-mr2

* commit '16330e24':
  Improve VelocityTracker numerical stability. (DO NOT MERGE)
parents 4e3ba25c 16330e24
Loading
Loading
Loading
Loading
+44 −291
Original line number Diff line number Diff line
@@ -16,8 +16,6 @@

package android.view;

import android.util.Config;
import android.util.Log;
import android.util.Poolable;
import android.util.Pool;
import android.util.Pools;
@@ -25,24 +23,15 @@ import android.util.PoolableManager;

/**
 * Helper for tracking the velocity of touch events, for implementing
 * flinging and other such gestures.  Use {@link #obtain} to retrieve a
 * new instance of the class when you are going to begin tracking, put
 * the motion events you receive into it with {@link #addMovement(MotionEvent)},
 * and when you want to determine the velocity call
 * {@link #computeCurrentVelocity(int)} and then {@link #getXVelocity()}
 * and {@link #getXVelocity()}.
 * flinging and other such gestures.
 *
 * Use {@link #obtain} to retrieve a new instance of the class when you are going
 * to begin tracking.  Put the motion events you receive into it with
 * {@link #addMovement(MotionEvent)}.  When you want to determine the velocity call
 * {@link #computeCurrentVelocity(int)} and then call {@link #getXVelocity(int)}
 * and {@link #getXVelocity(int)} to retrieve the velocity for each pointer id.
 */
public final class VelocityTracker implements Poolable<VelocityTracker> {
    private static final String TAG = "VelocityTracker";
    private static final boolean DEBUG = false;
    private static final boolean localLOGV = DEBUG || Config.LOGV;

    private static final int NUM_PAST = 10;
    private static final int MAX_AGE_MILLISECONDS = 200;
    
    private static final int POINTER_POOL_CAPACITY = 20;
    private static final int INVALID_POINTER = -1;

    private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool(
            Pools.finitePool(new PoolableManager<VelocityTracker>() {
                public VelocityTracker newInstance() {
@@ -57,30 +46,19 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
                }
            }, 2));

    private static Pointer sRecycledPointerListHead;
    private static int sRecycledPointerCount;
    
    private static final class Pointer {
        public Pointer next;
        
        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
        
        public int generation;
    }
    
    private Pointer mPointerListHead; // sorted by id in increasing order
    private int mLastTouchIndex;
    private int mGeneration;
    private int mActivePointerId;
    private static final int ACTIVE_POINTER_ID = -1;

    private int mPtr;
    private VelocityTracker mNext;

    private static native int nativeInitialize();
    private static native void nativeDispose(int ptr);
    private static native void nativeClear(int ptr);
    private static native void nativeAddMovement(int ptr, MotionEvent event);
    private static native void nativeComputeCurrentVelocity(int ptr, int units, float maxVelocity);
    private static native float nativeGetXVelocity(int ptr, int id);
    private static native float nativeGetYVelocity(int ptr, int id);

    /**
     * Retrieve a new VelocityTracker object to watch the velocity of a
     * motion.  Be sure to call {@link #recycle} when done.  You should
@@ -116,18 +94,26 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
    }

    private VelocityTracker() {
        clear();
        mPtr = nativeInitialize();
    }

    @Override
    protected void finalize() throws Throwable {
        try {
            if (mPtr != 0) {
                nativeDispose(mPtr);
                mPtr = 0;
            }
        } finally {
            super.finalize();
        }
    }

    /**
     * Reset the velocity tracker back to its initial state.
     */
    public void clear() {
        releasePointerList(mPointerListHead);
        
        mPointerListHead = null;
        mLastTouchIndex = 0;
        mActivePointerId = INVALID_POINTER;
        nativeClear(mPtr);
    }
    
    /**
@@ -137,110 +123,13 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
     * final {@link MotionEvent#ACTION_UP}.  You can, however, call this
     * for whichever events you desire.
     * 
     * @param ev The MotionEvent you received and would like to track.
     * @param event The MotionEvent you received and would like to track.
     */
    public void addMovement(MotionEvent ev) {
        final int historySize = ev.getHistorySize();
        final int pointerCount = ev.getPointerCount();
        final int lastTouchIndex = mLastTouchIndex;
        final int nextTouchIndex = (lastTouchIndex + 1) % NUM_PAST;
        final int finalTouchIndex = (nextTouchIndex + historySize) % NUM_PAST;
        final int generation = mGeneration++;
        
        mLastTouchIndex = finalTouchIndex;

        // Update pointer data.
        Pointer previousPointer = null;
        for (int i = 0; i < pointerCount; i++){
            final int pointerId = ev.getPointerId(i);
            
            // Find the pointer data for this pointer id.
            // This loop is optimized for the common case where pointer ids in the event
            // are in sorted order.  However, we check for this case explicitly and
            // perform a full linear scan from the start if needed.
            Pointer nextPointer;
            if (previousPointer == null || pointerId < previousPointer.id) {
                previousPointer = null;
                nextPointer = mPointerListHead;
            } else {
                nextPointer = previousPointer.next;
            }
            
            final Pointer pointer;
            for (;;) {
                if (nextPointer != null) {
                    final int nextPointerId = nextPointer.id;
                    if (nextPointerId == pointerId) {
                        pointer = nextPointer;
                        break;
                    }
                    if (nextPointerId < pointerId) {
                        nextPointer = nextPointer.next;
                        continue;
                    }
                }
                
                // Pointer went down.  Add it to the list.
                // Write a sentinel at the end of the pastTime trace so we will be able to
                // tell when the trace started.
                if (mActivePointerId == INVALID_POINTER) {
                    // Congratulations! You're the new active pointer!
                    mActivePointerId = pointerId;
                }
                pointer = obtainPointer();
                pointer.id = pointerId;
                pointer.pastTime[lastTouchIndex] = Long.MIN_VALUE;
                pointer.next = nextPointer;
                if (previousPointer == null) {
                    mPointerListHead = pointer;
                } else {
                    previousPointer.next = pointer;
                }
                break;
            }
            
            pointer.generation = generation;
            previousPointer = pointer;
            
            final float[] pastX = pointer.pastX;
            final float[] pastY = pointer.pastY;
            final long[] pastTime = pointer.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();
        }
        
        // Find removed pointers.
        previousPointer = null;
        for (Pointer pointer = mPointerListHead; pointer != null; ) {
            final Pointer nextPointer = pointer.next;
            final int pointerId = pointer.id;
            if (pointer.generation != generation) {
                // Pointer went up.  Remove it from the list.
                if (previousPointer == null) {
                    mPointerListHead = nextPointer;
                } else {
                    previousPointer.next = nextPointer;
                }
                releasePointer(pointer);

                if (pointerId == mActivePointerId) {
                    // Pick a new active pointer. How is arbitrary.
                    mActivePointerId = mPointerListHead != null ?
                            mPointerListHead.id : INVALID_POINTER;
                }
            } else {
                previousPointer = pointer;
            }
            pointer = nextPointer;
    public void addMovement(MotionEvent event) {
        if (event == null) {
            throw new IllegalArgumentException("event must not be null");
        }
        nativeAddMovement(mPtr, event);
    }

    /**
@@ -250,7 +139,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
     * @see #computeCurrentVelocity(int, float) 
     */
    public void computeCurrentVelocity(int units) {
        computeCurrentVelocity(units, Float.MAX_VALUE);
        nativeComputeCurrentVelocity(mPtr, units, Float.MAX_VALUE);
    }

    /**
@@ -267,78 +156,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
     * must be positive.
     */
    public void computeCurrentVelocity(int units, float maxVelocity) {
        final int lastTouchIndex = mLastTouchIndex;
        
        for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) {
            final long[] pastTime = pointer.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[] pastX = pointer.pastX;
            final float[] pastY = pointer.pastY;
            
            final float oldestX = pastX[oldestTouchIndex];
            final float oldestY = pastY[oldestTouchIndex];
            final long oldestTime = pastTime[oldestTouchIndex];
            
            float accumX = 0;
            float accumY = 0;
            
            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;
            }
            
            if (accumX < -maxVelocity) {
                accumX = - maxVelocity;
            } else if (accumX > maxVelocity) {
                accumX = maxVelocity;
            }
            
            if (accumY < -maxVelocity) {
                accumY = - maxVelocity;
            } else if (accumY > maxVelocity) {
                accumY = maxVelocity;
            }
            
            pointer.xVelocity = accumX;
            pointer.yVelocity = accumY;
            
            if (localLOGV) {
                Log.v(TAG, "Pointer " + pointer.id
                    + ": Y velocity=" + accumX +" X velocity=" + accumY + " N=" + numTouches);
            }
        }
        nativeComputeCurrentVelocity(mPtr, units, maxVelocity);
    }
    
    /**
@@ -348,8 +166,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
     * @return The previously computed X velocity.
     */
    public float getXVelocity() {
        Pointer pointer = getPointer(mActivePointerId);
        return pointer != null ? pointer.xVelocity : 0;
        return nativeGetXVelocity(mPtr, ACTIVE_POINTER_ID);
    }
    
    /**
@@ -359,8 +176,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
     * @return The previously computed Y velocity.
     */
    public float getYVelocity() {
        Pointer pointer = getPointer(mActivePointerId);
        return pointer != null ? pointer.yVelocity : 0;
        return nativeGetYVelocity(mPtr, ACTIVE_POINTER_ID);
    }
    
    /**
@@ -371,8 +187,7 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
     * @return The previously computed X velocity.
     */
    public float getXVelocity(int id) {
        Pointer pointer = getPointer(id);
        return pointer != null ? pointer.xVelocity : 0;
        return nativeGetXVelocity(mPtr, id);
    }
    
    /**
@@ -383,68 +198,6 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
     * @return The previously computed Y velocity.
     */
    public float getYVelocity(int id) {
        Pointer pointer = getPointer(id);
        return pointer != null ? pointer.yVelocity : 0;
    }
    
    private Pointer getPointer(int id) {
        for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) {
            if (pointer.id == id) {
                return pointer;
            }
        }
        return null;
    }
    
    private static Pointer obtainPointer() {
        synchronized (sPool) {
            if (sRecycledPointerCount != 0) {
                Pointer element = sRecycledPointerListHead;
                sRecycledPointerCount -= 1;
                sRecycledPointerListHead = element.next;
                element.next = null;
                return element;
            }
        }
        return new Pointer();
    }
    
    private static void releasePointer(Pointer pointer) {
        synchronized (sPool) {
            if (sRecycledPointerCount < POINTER_POOL_CAPACITY) {
                pointer.next = sRecycledPointerListHead;
                sRecycledPointerCount += 1;
                sRecycledPointerListHead = pointer;
            }
        }
    }
    
    private static void releasePointerList(Pointer pointer) {
        if (pointer != null) {
            synchronized (sPool) {
                int count = sRecycledPointerCount;
                if (count >= POINTER_POOL_CAPACITY) {
                    return;
                }
                
                Pointer tail = pointer;
                for (;;) {
                    count += 1;
                    if (count >= POINTER_POOL_CAPACITY) {
                        break;
                    }
                    
                    Pointer next = tail.next;
                    if (next == null) {
                        break;
                    }
                    tail = next;
                }

                tail.next = sRecycledPointerListHead;
                sRecycledPointerCount = count;
                sRecycledPointerListHead = pointer;
            }
        }
        return nativeGetYVelocity(mPtr, id);
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@ LOCAL_SRC_FILES:= \
	android_view_KeyCharacterMap.cpp \
	android_view_GLES20Canvas.cpp \
	android_view_MotionEvent.cpp \
	android_view_VelocityTracker.cpp \
	android_text_AndroidCharacter.cpp \
	android_text_AndroidBidi.cpp \
	android_os_Debug.cpp \
+2 −0
Original line number Diff line number Diff line
@@ -170,6 +170,7 @@ extern int register_android_view_InputChannel(JNIEnv* env);
extern int register_android_view_InputQueue(JNIEnv* env);
extern int register_android_view_KeyEvent(JNIEnv* env);
extern int register_android_view_MotionEvent(JNIEnv* env);
extern int register_android_view_VelocityTracker(JNIEnv* env);
extern int register_android_content_res_ObbScanner(JNIEnv* env);
extern int register_android_content_res_Configuration(JNIEnv* env);
extern int register_android_animation_PropertyValuesHolder(JNIEnv *env);
@@ -1302,6 +1303,7 @@ static const RegJNIRec gRegJNI[] = {
    REG_JNI(register_android_view_InputQueue),
    REG_JNI(register_android_view_KeyEvent),
    REG_JNI(register_android_view_MotionEvent),
    REG_JNI(register_android_view_VelocityTracker),

    REG_JNI(register_android_content_res_ObbScanner),
    REG_JNI(register_android_content_res_Configuration),
+1 −1
Original line number Diff line number Diff line
@@ -380,7 +380,7 @@ int NativeInputQueue::handleReceiveCallback(int receiveFd, int events, void* dat
#if DEBUG_DISPATCH_CYCLE
        LOGD("channel '%s' ~ Received motion event.", connection->getInputChannelName());
#endif
        inputEventObj = android_view_MotionEvent_fromNative(env,
        inputEventObj = android_view_MotionEvent_obtainAsCopy(env,
                static_cast<MotionEvent*>(inputEvent));
        dispatchMethodId = gInputQueueClassInfo.dispatchMotionEvent;
        break;
+7 −22
Original line number Diff line number Diff line
@@ -59,7 +59,10 @@ static struct {

// ----------------------------------------------------------------------------

static MotionEvent* android_view_MotionEvent_getNativePtr(JNIEnv* env, jobject eventObj) {
MotionEvent* android_view_MotionEvent_getNativePtr(JNIEnv* env, jobject eventObj) {
    if (!eventObj) {
        return NULL;
    }
    return reinterpret_cast<MotionEvent*>(
            env->GetIntField(eventObj, gMotionEventClassInfo.mNativePtr));
}
@@ -70,10 +73,10 @@ static void android_view_MotionEvent_setNativePtr(JNIEnv* env, jobject eventObj,
            reinterpret_cast<int>(event));
}

jobject android_view_MotionEvent_fromNative(JNIEnv* env, const MotionEvent* event) {
jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent* event) {
    jobject eventObj = env->CallStaticObjectMethod(gMotionEventClassInfo.clazz,
            gMotionEventClassInfo.obtain);
    if (env->ExceptionCheck()) {
    if (env->ExceptionCheck() || !eventObj) {
        LOGE("An exception occurred while obtaining a motion event.");
        LOGE_EX(env);
        env->ExceptionClear();
@@ -90,18 +93,6 @@ jobject android_view_MotionEvent_fromNative(JNIEnv* env, const MotionEvent* even
    return eventObj;
}

status_t android_view_MotionEvent_toNative(JNIEnv* env, jobject eventObj,
        MotionEvent* event) {
    MotionEvent* srcEvent = android_view_MotionEvent_getNativePtr(env, eventObj);
    if (!srcEvent) {
        LOGE("MotionEvent was finalized");
        return BAD_VALUE;
    }

    event->copyFrom(srcEvent, true);
    return OK;
}

status_t android_view_MotionEvent_recycle(JNIEnv* env, jobject eventObj) {
    env->CallVoidMethod(eventObj, gMotionEventClassInfo.recycle);
    if (env->ExceptionCheck()) {
@@ -502,13 +493,7 @@ static jint android_view_MotionEvent_nativeGetPointerId(JNIEnv* env, jclass claz
static jint android_view_MotionEvent_nativeFindPointerIndex(JNIEnv* env, jclass clazz,
        jint nativePtr, jint pointerId) {
    MotionEvent* event = reinterpret_cast<MotionEvent*>(nativePtr);
    size_t pointerCount = event->getPointerCount();
    for (size_t i = 0; i < pointerCount; i++) {
        if (event->getPointerId(i) == pointerId) {
            return i;
        }
    }
    return -1;
    return jint(event->findPointerIndex(pointerId));
}

static jint android_view_MotionEvent_nativeGetHistorySize(JNIEnv* env, jclass clazz,
Loading