Merge "Improve VelocityTracker numerical stability. (DO NOT MERGE)" into honeycomb-mr2
This commit is contained in:
@ -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() {
|
||||
@ -56,31 +45,20 @@ public final class VelocityTracker implements Poolable<VelocityTracker> {
|
||||
element.clear();
|
||||
}
|
||||
}, 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);
|
||||
}
|
||||
}
|
||||
|
@ -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 \
|
||||
|
@ -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),
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -26,12 +26,11 @@ class MotionEvent;
|
||||
|
||||
/* Obtains an instance of a DVM MotionEvent object as a copy of a native MotionEvent instance.
|
||||
* Returns NULL on error. */
|
||||
extern jobject android_view_MotionEvent_fromNative(JNIEnv* env, const MotionEvent* event);
|
||||
extern jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const MotionEvent* event);
|
||||
|
||||
/* Copies the contents of a DVM MotionEvent object to a native MotionEvent instance.
|
||||
* Returns non-zero on error. */
|
||||
extern status_t android_view_MotionEvent_toNative(JNIEnv* env, jobject eventObj,
|
||||
MotionEvent* event);
|
||||
/* Gets the underlying native MotionEvent instance within a DVM MotionEvent object.
|
||||
* Returns NULL if the event is NULL or if it is uninitialized. */
|
||||
extern MotionEvent* android_view_MotionEvent_getNativePtr(JNIEnv* env, jobject eventObj);
|
||||
|
||||
/* Recycles a DVM MotionEvent object.
|
||||
* Returns non-zero on error. */
|
||||
|
208
core/jni/android_view_VelocityTracker.cpp
Normal file
208
core/jni/android_view_VelocityTracker.cpp
Normal file
@ -0,0 +1,208 @@
|
||||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#define LOG_TAG "VelocityTracker-JNI"
|
||||
|
||||
#include "JNIHelp.h"
|
||||
|
||||
#include <android_runtime/AndroidRuntime.h>
|
||||
#include <utils/Log.h>
|
||||
#include <ui/Input.h>
|
||||
#include "android_view_MotionEvent.h"
|
||||
|
||||
|
||||
namespace android {
|
||||
|
||||
// Special constant to request the velocity of the active pointer.
|
||||
static const int ACTIVE_POINTER_ID = -1;
|
||||
|
||||
// --- VelocityTrackerState ---
|
||||
|
||||
class VelocityTrackerState {
|
||||
public:
|
||||
VelocityTrackerState();
|
||||
|
||||
void clear();
|
||||
void addMovement(const MotionEvent* event);
|
||||
void computeCurrentVelocity(int32_t units, float maxVelocity);
|
||||
void getVelocity(int32_t id, float* outVx, float* outVy);
|
||||
|
||||
private:
|
||||
struct Velocity {
|
||||
float vx, vy;
|
||||
};
|
||||
|
||||
VelocityTracker mVelocityTracker;
|
||||
int32_t mActivePointerId;
|
||||
BitSet32 mCalculatedIdBits;
|
||||
Velocity mCalculatedVelocity[MAX_POINTERS];
|
||||
};
|
||||
|
||||
VelocityTrackerState::VelocityTrackerState() : mActivePointerId(-1) {
|
||||
}
|
||||
|
||||
void VelocityTrackerState::clear() {
|
||||
mVelocityTracker.clear();
|
||||
mActivePointerId = -1;
|
||||
mCalculatedIdBits.clear();
|
||||
}
|
||||
|
||||
void VelocityTrackerState::addMovement(const MotionEvent* event) {
|
||||
mVelocityTracker.addMovement(event);
|
||||
}
|
||||
|
||||
void VelocityTrackerState::computeCurrentVelocity(int32_t units, float maxVelocity) {
|
||||
BitSet32 idBits(mVelocityTracker.getCurrentPointerIdBits());
|
||||
mCalculatedIdBits = idBits;
|
||||
|
||||
for (uint32_t index = 0; !idBits.isEmpty(); index++) {
|
||||
uint32_t id = idBits.firstMarkedBit();
|
||||
idBits.clearBit(id);
|
||||
|
||||
float vx, vy;
|
||||
mVelocityTracker.getVelocity(id, &vx, &vy);
|
||||
|
||||
vx = vx * units / 1000;
|
||||
vy = vy * units / 1000;
|
||||
|
||||
if (vx > maxVelocity) {
|
||||
vx = maxVelocity;
|
||||
} else if (vx < -maxVelocity) {
|
||||
vx = -maxVelocity;
|
||||
}
|
||||
if (vy > maxVelocity) {
|
||||
vy = maxVelocity;
|
||||
} else if (vy < -maxVelocity) {
|
||||
vy = -maxVelocity;
|
||||
}
|
||||
|
||||
Velocity& velocity = mCalculatedVelocity[index];
|
||||
velocity.vx = vx;
|
||||
velocity.vy = vy;
|
||||
}
|
||||
}
|
||||
|
||||
void VelocityTrackerState::getVelocity(int32_t id, float* outVx, float* outVy) {
|
||||
if (id == ACTIVE_POINTER_ID) {
|
||||
id = mVelocityTracker.getActivePointerId();
|
||||
}
|
||||
|
||||
float vx, vy;
|
||||
if (id >= 0 && id <= MAX_POINTER_ID && mCalculatedIdBits.hasBit(id)) {
|
||||
uint32_t index = mCalculatedIdBits.getIndexOfBit(id);
|
||||
const Velocity& velocity = mCalculatedVelocity[index];
|
||||
vx = velocity.vx;
|
||||
vy = velocity.vy;
|
||||
} else {
|
||||
vx = 0;
|
||||
vy = 0;
|
||||
}
|
||||
|
||||
if (outVx) {
|
||||
*outVx = vx;
|
||||
}
|
||||
if (outVy) {
|
||||
*outVy = vy;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- JNI Methods ---
|
||||
|
||||
static jint android_view_VelocityTracker_nativeInitialize(JNIEnv* env, jclass clazz) {
|
||||
return reinterpret_cast<jint>(new VelocityTrackerState());
|
||||
}
|
||||
|
||||
static void android_view_VelocityTracker_nativeDispose(JNIEnv* env, jclass clazz, jint ptr) {
|
||||
VelocityTrackerState* state = reinterpret_cast<VelocityTrackerState*>(ptr);
|
||||
delete state;
|
||||
}
|
||||
|
||||
static void android_view_VelocityTracker_nativeClear(JNIEnv* env, jclass clazz, jint ptr) {
|
||||
VelocityTrackerState* state = reinterpret_cast<VelocityTrackerState*>(ptr);
|
||||
state->clear();
|
||||
}
|
||||
|
||||
static void android_view_VelocityTracker_nativeAddMovement(JNIEnv* env, jclass clazz, jint ptr,
|
||||
jobject eventObj) {
|
||||
const MotionEvent* event = android_view_MotionEvent_getNativePtr(env, eventObj);
|
||||
if (!event) {
|
||||
LOGW("nativeAddMovement failed because MotionEvent was finalized.");
|
||||
return;
|
||||
}
|
||||
|
||||
VelocityTrackerState* state = reinterpret_cast<VelocityTrackerState*>(ptr);
|
||||
state->addMovement(event);
|
||||
}
|
||||
|
||||
static void android_view_VelocityTracker_nativeComputeCurrentVelocity(JNIEnv* env, jclass clazz,
|
||||
jint ptr, jint units, jfloat maxVelocity) {
|
||||
VelocityTrackerState* state = reinterpret_cast<VelocityTrackerState*>(ptr);
|
||||
state->computeCurrentVelocity(units, maxVelocity);
|
||||
}
|
||||
|
||||
static jfloat android_view_VelocityTracker_nativeGetXVelocity(JNIEnv* env, jclass clazz,
|
||||
jint ptr, jint id) {
|
||||
VelocityTrackerState* state = reinterpret_cast<VelocityTrackerState*>(ptr);
|
||||
float vx;
|
||||
state->getVelocity(id, &vx, NULL);
|
||||
return vx;
|
||||
}
|
||||
|
||||
static jfloat android_view_VelocityTracker_nativeGetYVelocity(JNIEnv* env, jclass clazz,
|
||||
jint ptr, jint id) {
|
||||
VelocityTrackerState* state = reinterpret_cast<VelocityTrackerState*>(ptr);
|
||||
float vy;
|
||||
state->getVelocity(id, NULL, &vy);
|
||||
return vy;
|
||||
}
|
||||
|
||||
|
||||
// --- JNI Registration ---
|
||||
|
||||
static JNINativeMethod gVelocityTrackerMethods[] = {
|
||||
/* name, signature, funcPtr */
|
||||
{ "nativeInitialize",
|
||||
"()I",
|
||||
(void*)android_view_VelocityTracker_nativeInitialize },
|
||||
{ "nativeDispose",
|
||||
"(I)V",
|
||||
(void*)android_view_VelocityTracker_nativeDispose },
|
||||
{ "nativeClear",
|
||||
"(I)V",
|
||||
(void*)android_view_VelocityTracker_nativeClear },
|
||||
{ "nativeAddMovement",
|
||||
"(ILandroid/view/MotionEvent;)V",
|
||||
(void*)android_view_VelocityTracker_nativeAddMovement },
|
||||
{ "nativeComputeCurrentVelocity",
|
||||
"(IIF)V",
|
||||
(void*)android_view_VelocityTracker_nativeComputeCurrentVelocity },
|
||||
{ "nativeGetXVelocity",
|
||||
"(II)F",
|
||||
(void*)android_view_VelocityTracker_nativeGetXVelocity },
|
||||
{ "nativeGetYVelocity",
|
||||
"(II)F",
|
||||
(void*)android_view_VelocityTracker_nativeGetYVelocity },
|
||||
};
|
||||
|
||||
int register_android_view_VelocityTracker(JNIEnv* env) {
|
||||
int res = jniRegisterNativeMethods(env, "android/view/VelocityTracker",
|
||||
gVelocityTrackerMethods, NELEM(gVelocityTrackerMethods));
|
||||
LOG_FATAL_IF(res < 0, "Unable to register native methods.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
} // namespace android
|
@ -313,6 +313,13 @@ public:
|
||||
|
||||
inline int32_t getAction() const { return mAction; }
|
||||
|
||||
inline int32_t getActionMasked() const { return mAction & AMOTION_EVENT_ACTION_MASK; }
|
||||
|
||||
inline int32_t getActionIndex() const {
|
||||
return (mAction & AMOTION_EVENT_ACTION_POINTER_INDEX_MASK)
|
||||
>> AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT;
|
||||
}
|
||||
|
||||
inline void setAction(int32_t action) { mAction = action; }
|
||||
|
||||
inline int32_t getFlags() const { return mFlags; }
|
||||
@ -460,6 +467,8 @@ public:
|
||||
AMOTION_EVENT_AXIS_ORIENTATION, pointerIndex, historicalIndex);
|
||||
}
|
||||
|
||||
ssize_t findPointerIndex(int32_t pointerId) const;
|
||||
|
||||
void initialize(
|
||||
int32_t deviceId,
|
||||
int32_t source,
|
||||
@ -553,8 +562,7 @@ private:
|
||||
};
|
||||
|
||||
/*
|
||||
* Calculates the velocity of pointer motions over time.
|
||||
* Uses essentially the same algorithm as android.view.VelocityTracker.
|
||||
* Calculates the velocity of pointer movements over time.
|
||||
*/
|
||||
class VelocityTracker {
|
||||
public:
|
||||
@ -567,6 +575,11 @@ public:
|
||||
// Resets the velocity tracker state.
|
||||
void clear();
|
||||
|
||||
// Resets the velocity tracker state for specific pointers.
|
||||
// Call this method when some pointers have changed and may be reusing
|
||||
// an id that was assigned to a different pointer earlier.
|
||||
void clearPointers(BitSet32 idBits);
|
||||
|
||||
// Adds movement information for a set of pointers.
|
||||
// The idBits bitfield specifies the pointer ids of the pointers whose positions
|
||||
// are included in the movement.
|
||||
@ -574,11 +587,20 @@ public:
|
||||
// increasing id. Its size should be equal to the number of one bits in idBits.
|
||||
void addMovement(nsecs_t eventTime, BitSet32 idBits, const Position* positions);
|
||||
|
||||
// Adds movement information for all pointers in a MotionEvent, including historical samples.
|
||||
void addMovement(const MotionEvent* event);
|
||||
|
||||
// Gets the velocity of the specified pointer id in position units per second.
|
||||
// Returns false and sets the velocity components to zero if there is no movement
|
||||
// information for the pointer.
|
||||
bool getVelocity(uint32_t id, float* outVx, float* outVy) const;
|
||||
|
||||
// Gets the active pointer id, or -1 if none.
|
||||
inline int32_t getActivePointerId() const { return mActivePointerId; }
|
||||
|
||||
// Gets a bitset containing all pointer ids from the most recent movement.
|
||||
inline BitSet32 getCurrentPointerIdBits() const { return mMovements[mIndex].idBits; }
|
||||
|
||||
private:
|
||||
// Number of samples to keep.
|
||||
static const uint32_t HISTORY_SIZE = 10;
|
||||
@ -587,7 +609,7 @@ private:
|
||||
static const nsecs_t MAX_AGE = 200 * 1000000; // 200 ms
|
||||
|
||||
// The minimum duration between samples when estimating velocity.
|
||||
static const nsecs_t MIN_DURATION = 5 * 1000000; // 5 ms
|
||||
static const nsecs_t MIN_DURATION = 10 * 1000000; // 10 ms
|
||||
|
||||
struct Movement {
|
||||
nsecs_t eventTime;
|
||||
@ -597,6 +619,7 @@ private:
|
||||
|
||||
uint32_t mIndex;
|
||||
Movement mMovements[HISTORY_SIZE];
|
||||
int32_t mActivePointerId;
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -61,6 +61,10 @@ struct BitSet32 {
|
||||
// Result is undefined if all bits are marked.
|
||||
inline uint32_t firstUnmarkedBit() const { return __builtin_clz(~ value); }
|
||||
|
||||
// Finds the last marked bit in the set.
|
||||
// Result is undefined if all bits are unmarked.
|
||||
inline uint32_t lastMarkedBit() const { return 31 - __builtin_ctz(value); }
|
||||
|
||||
// Gets the index of the specified bit in the set, which is the number of
|
||||
// marked bits that appear before the specified bit.
|
||||
inline uint32_t getIndexOfBit(uint32_t n) const {
|
||||
|
@ -483,6 +483,16 @@ float MotionEvent::getHistoricalAxisValue(int32_t axis, size_t pointerIndex,
|
||||
return value;
|
||||
}
|
||||
|
||||
ssize_t MotionEvent::findPointerIndex(int32_t pointerId) const {
|
||||
size_t pointerCount = mPointerIds.size();
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
if (mPointerIds.itemAt(i) == pointerId) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void MotionEvent::offsetLocation(float xOffset, float yOffset) {
|
||||
mXOffset += xOffset;
|
||||
mYOffset += yOffset;
|
||||
@ -667,12 +677,27 @@ VelocityTracker::VelocityTracker() {
|
||||
void VelocityTracker::clear() {
|
||||
mIndex = 0;
|
||||
mMovements[0].idBits.clear();
|
||||
mActivePointerId = -1;
|
||||
}
|
||||
|
||||
void VelocityTracker::clearPointers(BitSet32 idBits) {
|
||||
BitSet32 remainingIdBits(mMovements[mIndex].idBits.value & ~idBits.value);
|
||||
mMovements[mIndex].idBits = remainingIdBits;
|
||||
|
||||
if (mActivePointerId >= 0 && idBits.hasBit(mActivePointerId)) {
|
||||
mActivePointerId = !remainingIdBits.isEmpty() ? remainingIdBits.firstMarkedBit() : -1;
|
||||
}
|
||||
}
|
||||
|
||||
void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Position* positions) {
|
||||
if (++mIndex == HISTORY_SIZE) {
|
||||
mIndex = 0;
|
||||
}
|
||||
|
||||
while (idBits.count() > MAX_POINTERS) {
|
||||
idBits.clearBit(idBits.lastMarkedBit());
|
||||
}
|
||||
|
||||
Movement& movement = mMovements[mIndex];
|
||||
movement.eventTime = eventTime;
|
||||
movement.idBits = idBits;
|
||||
@ -681,8 +706,13 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Posi
|
||||
movement.positions[i] = positions[i];
|
||||
}
|
||||
|
||||
if (mActivePointerId < 0 || !idBits.hasBit(mActivePointerId)) {
|
||||
mActivePointerId = count != 0 ? idBits.firstMarkedBit() : -1;
|
||||
}
|
||||
|
||||
#if DEBUG_VELOCITY
|
||||
LOGD("VelocityTracker: addMovement eventTime=%lld, idBits=0x%08x", eventTime, idBits.value);
|
||||
LOGD("VelocityTracker: addMovement eventTime=%lld, idBits=0x%08x, activePointerId=%d",
|
||||
eventTime, idBits.value, mActivePointerId);
|
||||
for (BitSet32 iterBits(idBits); !iterBits.isEmpty(); ) {
|
||||
uint32_t id = iterBits.firstMarkedBit();
|
||||
uint32_t index = idBits.getIndexOfBit(id);
|
||||
@ -690,7 +720,7 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Posi
|
||||
float vx, vy;
|
||||
bool available = getVelocity(id, &vx, &vy);
|
||||
if (available) {
|
||||
LOGD(" %d: position (%0.3f, %0.3f), velocity (%0.3f, %0.3f), speed %0.3f",
|
||||
LOGD(" %d: position (%0.3f, %0.3f), vx=%0.3f, vy=%0.3f, speed=%0.3f",
|
||||
id, positions[index].x, positions[index].y, vx, vy, sqrtf(vx * vx + vy * vy));
|
||||
} else {
|
||||
assert(vx == 0 && vy == 0);
|
||||
@ -701,6 +731,70 @@ void VelocityTracker::addMovement(nsecs_t eventTime, BitSet32 idBits, const Posi
|
||||
#endif
|
||||
}
|
||||
|
||||
void VelocityTracker::addMovement(const MotionEvent* event) {
|
||||
int32_t actionMasked = event->getActionMasked();
|
||||
|
||||
switch (actionMasked) {
|
||||
case AMOTION_EVENT_ACTION_DOWN:
|
||||
// Clear all pointers on down before adding the new movement.
|
||||
clear();
|
||||
break;
|
||||
case AMOTION_EVENT_ACTION_POINTER_DOWN: {
|
||||
// Start a new movement trace for a pointer that just went down.
|
||||
// We do this on down instead of on up because the client may want to query the
|
||||
// final velocity for a pointer that just went up.
|
||||
BitSet32 downIdBits;
|
||||
downIdBits.markBit(event->getActionIndex());
|
||||
clearPointers(downIdBits);
|
||||
break;
|
||||
}
|
||||
case AMOTION_EVENT_ACTION_OUTSIDE:
|
||||
case AMOTION_EVENT_ACTION_CANCEL:
|
||||
case AMOTION_EVENT_ACTION_SCROLL:
|
||||
case AMOTION_EVENT_ACTION_UP:
|
||||
case AMOTION_EVENT_ACTION_POINTER_UP:
|
||||
// Ignore these actions because they do not convey any new information about
|
||||
// pointer movement. We also want to preserve the last known velocity of the pointers.
|
||||
// Note that ACTION_UP and ACTION_POINTER_UP always report the last known position
|
||||
// of the pointers that went up. ACTION_POINTER_UP does include the new position of
|
||||
// pointers that remained down but we will also receive an ACTION_MOVE with this
|
||||
// information if any of them actually moved. Since we don't know how many pointers
|
||||
// will be going up at once it makes sense to just wait for the following ACTION_MOVE
|
||||
// before adding the movement.
|
||||
return;
|
||||
}
|
||||
|
||||
size_t pointerCount = event->getPointerCount();
|
||||
if (pointerCount > MAX_POINTERS) {
|
||||
pointerCount = MAX_POINTERS;
|
||||
}
|
||||
|
||||
BitSet32 idBits;
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
idBits.markBit(event->getPointerId(i));
|
||||
}
|
||||
|
||||
nsecs_t eventTime;
|
||||
Position positions[pointerCount];
|
||||
|
||||
size_t historySize = event->getHistorySize();
|
||||
for (size_t h = 0; h < historySize; h++) {
|
||||
eventTime = event->getHistoricalEventTime(h);
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
positions[i].x = event->getHistoricalX(i, h);
|
||||
positions[i].y = event->getHistoricalY(i, h);
|
||||
}
|
||||
addMovement(eventTime, idBits, positions);
|
||||
}
|
||||
|
||||
eventTime = event->getEventTime();
|
||||
for (size_t i = 0; i < pointerCount; i++) {
|
||||
positions[i].x = event->getX(i);
|
||||
positions[i].y = event->getY(i);
|
||||
}
|
||||
addMovement(eventTime, idBits, positions);
|
||||
}
|
||||
|
||||
bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const {
|
||||
const Movement& newestMovement = mMovements[mIndex];
|
||||
if (newestMovement.idBits.hasBit(id)) {
|
||||
@ -718,36 +812,17 @@ bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const
|
||||
oldestIndex = nextOldestIndex;
|
||||
} while (++numTouches < HISTORY_SIZE);
|
||||
|
||||
// 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.
|
||||
// Calculate an exponentially weighted moving average of the velocity estimate
|
||||
// at different points in time measured relative to the oldest sample.
|
||||
// This is essentially an IIR filter. Newer samples are weighted more heavily
|
||||
// than older samples. Samples at equal time points are weighted more or less
|
||||
// equally.
|
||||
//
|
||||
// NOTE: This condition exists in the android.view.VelocityTracker and imposes a
|
||||
// bias against the most recent data.
|
||||
if (numTouches > 3) {
|
||||
numTouches -= 1;
|
||||
}
|
||||
|
||||
// Calculate an exponentially weighted moving average of the velocity at different
|
||||
// points in time measured relative to the oldest samples. This is essentially
|
||||
// an IIR filter.
|
||||
//
|
||||
// One problem with this algorithm is that the sample data may be poorly conditioned.
|
||||
// One tricky problem is that the sample data may be poorly conditioned.
|
||||
// Sometimes samples arrive very close together in time which can cause us to
|
||||
// overestimate the velocity at that time point. Most samples might be measured
|
||||
// 16ms apart but some consecutive samples could be only 0.5sm apart due to
|
||||
// the way they are reported by the hardware or driver (sometimes in bursts or with
|
||||
// significant jitter). The instantaneous velocity for those samples 0.5ms apart will
|
||||
// be calculated to be 32 times what it should have been.
|
||||
// To work around this effect, we impose a minimum duration on the samples.
|
||||
//
|
||||
// FIXME: Samples close together in time can have an disproportionately large
|
||||
// impact on the result because all samples are equally weighted. The average should
|
||||
// instead take the time factor into account.
|
||||
//
|
||||
// FIXME: The minimum duration condition does not exist in
|
||||
// android.view.VelocityTracker yet. It is less important there because sample times
|
||||
// are truncated to the millisecond so back to back samples will often appear to be
|
||||
// zero milliseconds apart and will be ignored if they are the oldest ones.
|
||||
// 16ms apart but some consecutive samples could be only 0.5sm apart because
|
||||
// the hardware or driver reports them irregularly or in bursts.
|
||||
float accumVx = 0;
|
||||
float accumVy = 0;
|
||||
uint32_t index = oldestIndex;
|
||||
@ -755,19 +830,27 @@ bool VelocityTracker::getVelocity(uint32_t id, float* outVx, float* outVy) const
|
||||
const Movement& oldestMovement = mMovements[oldestIndex];
|
||||
const Position& oldestPosition =
|
||||
oldestMovement.positions[oldestMovement.idBits.getIndexOfBit(id)];
|
||||
nsecs_t lastDuration = 0;
|
||||
while (numTouches-- > 1) {
|
||||
if (++index == HISTORY_SIZE) {
|
||||
index = 0;
|
||||
}
|
||||
const Movement& movement = mMovements[index];
|
||||
nsecs_t duration = movement.eventTime - oldestMovement.eventTime;
|
||||
if (duration > MIN_DURATION) {
|
||||
|
||||
// If the duration between samples is small, we may significantly overestimate
|
||||
// the velocity. Consequently, we impose a minimum duration constraint on the
|
||||
// samples that we include in the calculation.
|
||||
if (duration >= MIN_DURATION) {
|
||||
const Position& position = movement.positions[movement.idBits.getIndexOfBit(id)];
|
||||
float scale = 1000000000.0f / duration; // one over time delta in seconds
|
||||
float vx = (position.x - oldestPosition.x) * scale;
|
||||
float vy = (position.y - oldestPosition.y) * scale;
|
||||
accumVx = accumVx == 0 ? vx : (accumVx + vx) * 0.5f;
|
||||
accumVy = accumVy == 0 ? vy : (accumVy + vy) * 0.5f;
|
||||
|
||||
accumVx = (accumVx * lastDuration + vx * duration) / (duration + lastDuration);
|
||||
accumVy = (accumVy * lastDuration + vy * duration) / (duration + lastDuration);
|
||||
|
||||
lastDuration = duration;
|
||||
samplesUsed += 1;
|
||||
}
|
||||
}
|
||||
|
@ -1024,15 +1024,14 @@ static jint android_server_InputManager_nativeInjectInputEvent(JNIEnv* env, jcla
|
||||
return gNativeInputManager->getInputManager()->getDispatcher()->injectInputEvent(
|
||||
& keyEvent, injectorPid, injectorUid, syncMode, timeoutMillis);
|
||||
} else if (env->IsInstanceOf(inputEventObj, gMotionEventClassInfo.clazz)) {
|
||||
MotionEvent motionEvent;
|
||||
status_t status = android_view_MotionEvent_toNative(env, inputEventObj, & motionEvent);
|
||||
if (status) {
|
||||
const MotionEvent* motionEvent = android_view_MotionEvent_getNativePtr(env, inputEventObj);
|
||||
if (!motionEvent) {
|
||||
jniThrowRuntimeException(env, "Could not read contents of MotionEvent object.");
|
||||
return INPUT_EVENT_INJECTION_FAILED;
|
||||
}
|
||||
|
||||
return gNativeInputManager->getInputManager()->getDispatcher()->injectInputEvent(
|
||||
& motionEvent, injectorPid, injectorUid, syncMode, timeoutMillis);
|
||||
motionEvent, injectorPid, injectorUid, syncMode, timeoutMillis);
|
||||
} else {
|
||||
jniThrowRuntimeException(env, "Invalid input event type.");
|
||||
return INPUT_EVENT_INJECTION_FAILED;
|
||||
|
289
tools/velocityplot/velocityplot.py
Executable file
289
tools/velocityplot/velocityplot.py
Executable file
@ -0,0 +1,289 @@
|
||||
#!/usr/bin/env python2.6
|
||||
#
|
||||
# Copyright (C) 2011 The Android Open Source Project
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
|
||||
#
|
||||
# Plots debug log output from VelocityTracker.
|
||||
# Enable DEBUG_VELOCITY to print the output.
|
||||
#
|
||||
# This code supports side-by-side comparison of two algorithms.
|
||||
# The old algorithm should be modified to emit debug log messages containing
|
||||
# the word "OLD".
|
||||
#
|
||||
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plot
|
||||
import subprocess
|
||||
import re
|
||||
import fcntl
|
||||
import os
|
||||
import errno
|
||||
import bisect
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
# Parameters.
|
||||
timespan = 15 # seconds total span shown
|
||||
scrolljump = 5 # seconds jump when scrolling
|
||||
timeticks = 1 # seconds between each time tick
|
||||
|
||||
# Non-blocking stream wrapper.
|
||||
class NonBlockingStream:
|
||||
def __init__(self, stream):
|
||||
fcntl.fcntl(stream, fcntl.F_SETFL, os.O_NONBLOCK)
|
||||
self.stream = stream
|
||||
self.buffer = ''
|
||||
self.pos = 0
|
||||
|
||||
def readline(self):
|
||||
while True:
|
||||
index = self.buffer.find('\n', self.pos)
|
||||
if index != -1:
|
||||
result = self.buffer[self.pos:index]
|
||||
self.pos = index + 1
|
||||
return result
|
||||
|
||||
self.buffer = self.buffer[self.pos:]
|
||||
self.pos = 0
|
||||
try:
|
||||
chunk = os.read(self.stream.fileno(), 4096)
|
||||
except OSError, e:
|
||||
if e.errno == errno.EAGAIN:
|
||||
return None
|
||||
raise e
|
||||
if len(chunk) == 0:
|
||||
if len(self.buffer) == 0:
|
||||
raise(EOFError)
|
||||
else:
|
||||
result = self.buffer
|
||||
self.buffer = ''
|
||||
self.pos = 0
|
||||
return result
|
||||
self.buffer += chunk
|
||||
|
||||
# Plotter
|
||||
class Plotter:
|
||||
def __init__(self, adbout):
|
||||
self.adbout = adbout
|
||||
|
||||
self.fig = plot.figure(1)
|
||||
self.fig.suptitle('Velocity Tracker', fontsize=12)
|
||||
self.fig.set_dpi(96)
|
||||
self.fig.set_size_inches(16, 12, forward=True)
|
||||
|
||||
self.velocity_x = self._make_timeseries()
|
||||
self.velocity_y = self._make_timeseries()
|
||||
self.velocity_magnitude = self._make_timeseries()
|
||||
self.velocity_axes = self._add_timeseries_axes(
|
||||
1, 'Velocity', 'px/s', [-5000, 5000],
|
||||
yticks=range(-5000, 5000, 1000))
|
||||
self.velocity_line_x = self._add_timeseries_line(
|
||||
self.velocity_axes, 'vx', 'red')
|
||||
self.velocity_line_y = self._add_timeseries_line(
|
||||
self.velocity_axes, 'vy', 'green')
|
||||
self.velocity_line_magnitude = self._add_timeseries_line(
|
||||
self.velocity_axes, 'magnitude', 'blue')
|
||||
self._add_timeseries_legend(self.velocity_axes)
|
||||
|
||||
shared_axis = self.velocity_axes
|
||||
|
||||
self.old_velocity_x = self._make_timeseries()
|
||||
self.old_velocity_y = self._make_timeseries()
|
||||
self.old_velocity_magnitude = self._make_timeseries()
|
||||
self.old_velocity_axes = self._add_timeseries_axes(
|
||||
2, 'Old Algorithm Velocity', 'px/s', [-5000, 5000],
|
||||
sharex=shared_axis,
|
||||
yticks=range(-5000, 5000, 1000))
|
||||
self.old_velocity_line_x = self._add_timeseries_line(
|
||||
self.old_velocity_axes, 'vx', 'red')
|
||||
self.old_velocity_line_y = self._add_timeseries_line(
|
||||
self.old_velocity_axes, 'vy', 'green')
|
||||
self.old_velocity_line_magnitude = self._add_timeseries_line(
|
||||
self.old_velocity_axes, 'magnitude', 'blue')
|
||||
self._add_timeseries_legend(self.old_velocity_axes)
|
||||
|
||||
self.timer = self.fig.canvas.new_timer(interval=100)
|
||||
self.timer.add_callback(lambda: self.update())
|
||||
self.timer.start()
|
||||
|
||||
self.timebase = None
|
||||
self._reset_parse_state()
|
||||
|
||||
# Initialize a time series.
|
||||
def _make_timeseries(self):
|
||||
return [[], []]
|
||||
|
||||
# Add a subplot to the figure for a time series.
|
||||
def _add_timeseries_axes(self, index, title, ylabel, ylim, yticks, sharex=None):
|
||||
num_graphs = 2
|
||||
height = 0.9 / num_graphs
|
||||
top = 0.95 - height * index
|
||||
axes = self.fig.add_axes([0.1, top, 0.8, height],
|
||||
xscale='linear',
|
||||
xlim=[0, timespan],
|
||||
ylabel=ylabel,
|
||||
yscale='linear',
|
||||
ylim=ylim,
|
||||
sharex=sharex)
|
||||
axes.text(0.02, 0.02, title, transform=axes.transAxes, fontsize=10, fontweight='bold')
|
||||
axes.set_xlabel('time (s)', fontsize=10, fontweight='bold')
|
||||
axes.set_ylabel(ylabel, fontsize=10, fontweight='bold')
|
||||
axes.set_xticks(range(0, timespan + 1, timeticks))
|
||||
axes.set_yticks(yticks)
|
||||
axes.grid(True)
|
||||
|
||||
for label in axes.get_xticklabels():
|
||||
label.set_fontsize(9)
|
||||
for label in axes.get_yticklabels():
|
||||
label.set_fontsize(9)
|
||||
|
||||
return axes
|
||||
|
||||
# Add a line to the axes for a time series.
|
||||
def _add_timeseries_line(self, axes, label, color, linewidth=1):
|
||||
return axes.plot([], label=label, color=color, linewidth=linewidth)[0]
|
||||
|
||||
# Add a legend to a time series.
|
||||
def _add_timeseries_legend(self, axes):
|
||||
axes.legend(
|
||||
loc='upper left',
|
||||
bbox_to_anchor=(1.01, 1),
|
||||
borderpad=0.1,
|
||||
borderaxespad=0.1,
|
||||
prop={'size': 10})
|
||||
|
||||
# Resets the parse state.
|
||||
def _reset_parse_state(self):
|
||||
self.parse_velocity_x = None
|
||||
self.parse_velocity_y = None
|
||||
self.parse_velocity_magnitude = None
|
||||
self.parse_old_velocity_x = None
|
||||
self.parse_old_velocity_y = None
|
||||
self.parse_old_velocity_magnitude = None
|
||||
|
||||
# Update samples.
|
||||
def update(self):
|
||||
timeindex = 0
|
||||
while True:
|
||||
try:
|
||||
line = self.adbout.readline()
|
||||
except EOFError:
|
||||
plot.close()
|
||||
return
|
||||
if line is None:
|
||||
break
|
||||
print line
|
||||
|
||||
try:
|
||||
timestamp = self._parse_timestamp(line)
|
||||
except ValueError, e:
|
||||
continue
|
||||
if self.timebase is None:
|
||||
self.timebase = timestamp
|
||||
delta = timestamp - self.timebase
|
||||
timeindex = delta.seconds + delta.microseconds * 0.000001
|
||||
|
||||
if line.find(': position') != -1:
|
||||
self.parse_velocity_x = self._get_following_number(line, 'vx=')
|
||||
self.parse_velocity_y = self._get_following_number(line, 'vy=')
|
||||
self.parse_velocity_magnitude = self._get_following_number(line, 'speed=')
|
||||
self._append(self.velocity_x, timeindex, self.parse_velocity_x)
|
||||
self._append(self.velocity_y, timeindex, self.parse_velocity_y)
|
||||
self._append(self.velocity_magnitude, timeindex, self.parse_velocity_magnitude)
|
||||
|
||||
if line.find(': OLD') != -1:
|
||||
self.parse_old_velocity_x = self._get_following_number(line, 'vx=')
|
||||
self.parse_old_velocity_y = self._get_following_number(line, 'vy=')
|
||||
self.parse_old_velocity_magnitude = self._get_following_number(line, 'speed=')
|
||||
self._append(self.old_velocity_x, timeindex, self.parse_old_velocity_x)
|
||||
self._append(self.old_velocity_y, timeindex, self.parse_old_velocity_y)
|
||||
self._append(self.old_velocity_magnitude, timeindex, self.parse_old_velocity_magnitude)
|
||||
|
||||
# Scroll the plots.
|
||||
if timeindex > timespan:
|
||||
bottom = int(timeindex) - timespan + scrolljump
|
||||
self.timebase += timedelta(seconds=bottom)
|
||||
self._scroll(self.velocity_x, bottom)
|
||||
self._scroll(self.velocity_y, bottom)
|
||||
self._scroll(self.velocity_magnitude, bottom)
|
||||
self._scroll(self.old_velocity_x, bottom)
|
||||
self._scroll(self.old_velocity_y, bottom)
|
||||
self._scroll(self.old_velocity_magnitude, bottom)
|
||||
|
||||
# Redraw the plots.
|
||||
self.velocity_line_x.set_data(self.velocity_x)
|
||||
self.velocity_line_y.set_data(self.velocity_y)
|
||||
self.velocity_line_magnitude.set_data(self.velocity_magnitude)
|
||||
self.old_velocity_line_x.set_data(self.old_velocity_x)
|
||||
self.old_velocity_line_y.set_data(self.old_velocity_y)
|
||||
self.old_velocity_line_magnitude.set_data(self.old_velocity_magnitude)
|
||||
|
||||
self.fig.canvas.draw_idle()
|
||||
|
||||
# Scroll a time series.
|
||||
def _scroll(self, timeseries, bottom):
|
||||
bottom_index = bisect.bisect_left(timeseries[0], bottom)
|
||||
del timeseries[0][:bottom_index]
|
||||
del timeseries[1][:bottom_index]
|
||||
for i, timeindex in enumerate(timeseries[0]):
|
||||
timeseries[0][i] = timeindex - bottom
|
||||
|
||||
# Extract a word following the specified prefix.
|
||||
def _get_following_word(self, line, prefix):
|
||||
prefix_index = line.find(prefix)
|
||||
if prefix_index == -1:
|
||||
return None
|
||||
start_index = prefix_index + len(prefix)
|
||||
delim_index = line.find(',', start_index)
|
||||
if delim_index == -1:
|
||||
return line[start_index:]
|
||||
else:
|
||||
return line[start_index:delim_index]
|
||||
|
||||
# Extract a number following the specified prefix.
|
||||
def _get_following_number(self, line, prefix):
|
||||
word = self._get_following_word(line, prefix)
|
||||
if word is None:
|
||||
return None
|
||||
return float(word)
|
||||
|
||||
# Add a value to a time series.
|
||||
def _append(self, timeseries, timeindex, number):
|
||||
timeseries[0].append(timeindex)
|
||||
timeseries[1].append(number)
|
||||
|
||||
# Parse the logcat timestamp.
|
||||
# Timestamp has the form '01-21 20:42:42.930'
|
||||
def _parse_timestamp(self, line):
|
||||
return datetime.strptime(line[0:18], '%m-%d %H:%M:%S.%f')
|
||||
|
||||
# Notice
|
||||
print "Velocity Tracker plotting tool"
|
||||
print "-----------------------------------------\n"
|
||||
print "Please enable debug logging and recompile the code."
|
||||
|
||||
# Start adb.
|
||||
print "Starting adb logcat.\n"
|
||||
|
||||
adb = subprocess.Popen(['adb', 'logcat', '-s', '-v', 'time', 'Input:*', 'VelocityTracker:*'],
|
||||
stdout=subprocess.PIPE)
|
||||
adbout = NonBlockingStream(adb.stdout)
|
||||
|
||||
# Prepare plotter.
|
||||
plotter = Plotter(adbout)
|
||||
plotter.update()
|
||||
|
||||
# Main loop.
|
||||
plot.show()
|
Reference in New Issue
Block a user