VelocityTracker.java revision 8ae0f3f6c7319717237af74c8d4aad2c082eef2b
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.view;
18
19import android.util.Config;
20import android.util.Log;
21import android.util.Poolable;
22import android.util.Pool;
23import android.util.Pools;
24import android.util.PoolableManager;
25
26/**
27 * Helper for tracking the velocity of touch events, for implementing
28 * flinging and other such gestures.  Use {@link #obtain} to retrieve a
29 * new instance of the class when you are going to begin tracking, put
30 * the motion events you receive into it with {@link #addMovement(MotionEvent)},
31 * and when you want to determine the velocity call
32 * {@link #computeCurrentVelocity(int)} and then {@link #getXVelocity()}
33 * and {@link #getXVelocity()}.
34 */
35public final class VelocityTracker implements Poolable<VelocityTracker> {
36    private static final String TAG = "VelocityTracker";
37    private static final boolean DEBUG = false;
38    private static final boolean localLOGV = DEBUG || Config.LOGV;
39
40    private static final int NUM_PAST = 10;
41    private static final int MAX_AGE_MILLISECONDS = 200;
42
43    private static final int POINTER_POOL_CAPACITY = 20;
44    private static final int INVALID_POINTER = -1;
45
46    private static final Pool<VelocityTracker> sPool = Pools.synchronizedPool(
47            Pools.finitePool(new PoolableManager<VelocityTracker>() {
48                public VelocityTracker newInstance() {
49                    return new VelocityTracker();
50                }
51
52                public void onAcquired(VelocityTracker element) {
53                }
54
55                public void onReleased(VelocityTracker element) {
56                    element.clear();
57                }
58            }, 2));
59
60    private static Pointer sRecycledPointerListHead;
61    private static int sRecycledPointerCount;
62
63    private static final class Pointer {
64        public Pointer next;
65
66        public int id;
67        public float xVelocity;
68        public float yVelocity;
69
70        public final float[] pastX = new float[NUM_PAST];
71        public final float[] pastY = new float[NUM_PAST];
72        public final long[] pastTime = new long[NUM_PAST]; // uses Long.MIN_VALUE as a sentinel
73
74        public int generation;
75    }
76
77    private Pointer mPointerListHead; // sorted by id in increasing order
78    private int mLastTouchIndex;
79    private int mGeneration;
80    private int mActivePointerId;
81
82    private VelocityTracker mNext;
83
84    /**
85     * Retrieve a new VelocityTracker object to watch the velocity of a
86     * motion.  Be sure to call {@link #recycle} when done.  You should
87     * generally only maintain an active object while tracking a movement,
88     * so that the VelocityTracker can be re-used elsewhere.
89     *
90     * @return Returns a new VelocityTracker.
91     */
92    static public VelocityTracker obtain() {
93        return sPool.acquire();
94    }
95
96    /**
97     * Return a VelocityTracker object back to be re-used by others.  You must
98     * not touch the object after calling this function.
99     */
100    public void recycle() {
101        sPool.release(this);
102    }
103
104    /**
105     * @hide
106     */
107    public void setNextPoolable(VelocityTracker element) {
108        mNext = element;
109    }
110
111    /**
112     * @hide
113     */
114    public VelocityTracker getNextPoolable() {
115        return mNext;
116    }
117
118    private VelocityTracker() {
119        clear();
120    }
121
122    /**
123     * Reset the velocity tracker back to its initial state.
124     */
125    public void clear() {
126        releasePointerList(mPointerListHead);
127
128        mPointerListHead = null;
129        mLastTouchIndex = 0;
130        mActivePointerId = INVALID_POINTER;
131    }
132
133    /**
134     * Add a user's movement to the tracker.  You should call this for the
135     * initial {@link MotionEvent#ACTION_DOWN}, the following
136     * {@link MotionEvent#ACTION_MOVE} events that you receive, and the
137     * final {@link MotionEvent#ACTION_UP}.  You can, however, call this
138     * for whichever events you desire.
139     *
140     * @param ev The MotionEvent you received and would like to track.
141     */
142    public void addMovement(MotionEvent ev) {
143        final int historySize = ev.getHistorySize();
144        final int pointerCount = ev.getPointerCount();
145        final int lastTouchIndex = mLastTouchIndex;
146        final int nextTouchIndex = (lastTouchIndex + 1) % NUM_PAST;
147        final int finalTouchIndex = (nextTouchIndex + historySize) % NUM_PAST;
148        final int generation = mGeneration++;
149
150        mLastTouchIndex = finalTouchIndex;
151
152        // Update pointer data.
153        Pointer previousPointer = null;
154        for (int i = 0; i < pointerCount; i++){
155            final int pointerId = ev.getPointerId(i);
156
157            // Find the pointer data for this pointer id.
158            // This loop is optimized for the common case where pointer ids in the event
159            // are in sorted order.  However, we check for this case explicitly and
160            // perform a full linear scan from the start if needed.
161            Pointer nextPointer;
162            if (previousPointer == null || pointerId < previousPointer.id) {
163                previousPointer = null;
164                nextPointer = mPointerListHead;
165            } else {
166                nextPointer = previousPointer.next;
167            }
168
169            final Pointer pointer;
170            for (;;) {
171                if (nextPointer != null) {
172                    final int nextPointerId = nextPointer.id;
173                    if (nextPointerId == pointerId) {
174                        pointer = nextPointer;
175                        break;
176                    }
177                    if (nextPointerId < pointerId) {
178                        nextPointer = nextPointer.next;
179                        continue;
180                    }
181                }
182
183                // Pointer went down.  Add it to the list.
184                // Write a sentinel at the end of the pastTime trace so we will be able to
185                // tell when the trace started.
186                if (mActivePointerId == INVALID_POINTER) {
187                    // Congratulations! You're the new active pointer!
188                    mActivePointerId = pointerId;
189                }
190                pointer = obtainPointer();
191                pointer.id = pointerId;
192                pointer.pastTime[lastTouchIndex] = Long.MIN_VALUE;
193                pointer.next = nextPointer;
194                if (previousPointer == null) {
195                    mPointerListHead = pointer;
196                } else {
197                    previousPointer.next = pointer;
198                }
199                break;
200            }
201
202            pointer.generation = generation;
203            previousPointer = pointer;
204
205            final float[] pastX = pointer.pastX;
206            final float[] pastY = pointer.pastY;
207            final long[] pastTime = pointer.pastTime;
208
209            for (int j = 0; j < historySize; j++) {
210                final int touchIndex = (nextTouchIndex + j) % NUM_PAST;
211                pastX[touchIndex] = ev.getHistoricalX(i, j);
212                pastY[touchIndex] = ev.getHistoricalY(i, j);
213                pastTime[touchIndex] = ev.getHistoricalEventTime(j);
214            }
215            pastX[finalTouchIndex] = ev.getX(i);
216            pastY[finalTouchIndex] = ev.getY(i);
217            pastTime[finalTouchIndex] = ev.getEventTime();
218        }
219
220        // Find removed pointers.
221        previousPointer = null;
222        for (Pointer pointer = mPointerListHead; pointer != null; ) {
223            final Pointer nextPointer = pointer.next;
224            final int pointerId = pointer.id;
225            if (pointer.generation != generation) {
226                // Pointer went up.  Remove it from the list.
227                if (previousPointer == null) {
228                    mPointerListHead = nextPointer;
229                } else {
230                    previousPointer.next = nextPointer;
231                }
232                releasePointer(pointer);
233
234                if (pointerId == mActivePointerId) {
235                    // Pick a new active pointer. How is arbitrary.
236                    mActivePointerId = mPointerListHead != null ?
237                            mPointerListHead.id : INVALID_POINTER;
238                }
239            } else {
240                previousPointer = pointer;
241            }
242            pointer = nextPointer;
243        }
244    }
245
246    /**
247     * Equivalent to invoking {@link #computeCurrentVelocity(int, float)} with a maximum
248     * velocity of Float.MAX_VALUE.
249     *
250     * @see #computeCurrentVelocity(int, float)
251     */
252    public void computeCurrentVelocity(int units) {
253        computeCurrentVelocity(units, Float.MAX_VALUE);
254    }
255
256    /**
257     * Compute the current velocity based on the points that have been
258     * collected.  Only call this when you actually want to retrieve velocity
259     * information, as it is relatively expensive.  You can then retrieve
260     * the velocity with {@link #getXVelocity()} and
261     * {@link #getYVelocity()}.
262     *
263     * @param units The units you would like the velocity in.  A value of 1
264     * provides pixels per millisecond, 1000 provides pixels per second, etc.
265     * @param maxVelocity The maximum velocity that can be computed by this method.
266     * This value must be declared in the same unit as the units parameter. This value
267     * must be positive.
268     */
269    public void computeCurrentVelocity(int units, float maxVelocity) {
270        final int lastTouchIndex = mLastTouchIndex;
271
272        for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) {
273            final long[] pastTime = pointer.pastTime;
274
275            // Search backwards in time for oldest acceptable time.
276            // Stop at the beginning of the trace as indicated by the sentinel time Long.MIN_VALUE.
277            int oldestTouchIndex = lastTouchIndex;
278            int numTouches = 1;
279            final long minTime = pastTime[lastTouchIndex] - MAX_AGE_MILLISECONDS;
280            while (numTouches < NUM_PAST) {
281                final int nextOldestTouchIndex = (oldestTouchIndex + NUM_PAST - 1) % NUM_PAST;
282                final long nextOldestTime = pastTime[nextOldestTouchIndex];
283                if (nextOldestTime < minTime) { // also handles end of trace sentinel
284                    break;
285                }
286                oldestTouchIndex = nextOldestTouchIndex;
287                numTouches += 1;
288            }
289
290            // If we have a lot of samples, skip the last received sample since it is
291            // probably pretty noisy compared to the sum of all of the traces already acquired.
292            if (numTouches > 3) {
293                numTouches -= 1;
294            }
295
296            // Kind-of stupid.
297            final float[] pastX = pointer.pastX;
298            final float[] pastY = pointer.pastY;
299
300            final float oldestX = pastX[oldestTouchIndex];
301            final float oldestY = pastY[oldestTouchIndex];
302            final long oldestTime = pastTime[oldestTouchIndex];
303
304            float accumX = 0;
305            float accumY = 0;
306
307            for (int i = 1; i < numTouches; i++) {
308                final int touchIndex = (oldestTouchIndex + i) % NUM_PAST;
309                final int duration = (int)(pastTime[touchIndex] - oldestTime);
310
311                if (duration == 0) continue;
312
313                float delta = pastX[touchIndex] - oldestX;
314                float velocity = (delta / duration) * units; // pixels/frame.
315                accumX = (accumX == 0) ? velocity : (accumX + velocity) * .5f;
316
317                delta = pastY[touchIndex] - oldestY;
318                velocity = (delta / duration) * units; // pixels/frame.
319                accumY = (accumY == 0) ? velocity : (accumY + velocity) * .5f;
320            }
321
322            if (accumX < -maxVelocity) {
323                accumX = - maxVelocity;
324            } else if (accumX > maxVelocity) {
325                accumX = maxVelocity;
326            }
327
328            if (accumY < -maxVelocity) {
329                accumY = - maxVelocity;
330            } else if (accumY > maxVelocity) {
331                accumY = maxVelocity;
332            }
333
334            pointer.xVelocity = accumX;
335            pointer.yVelocity = accumY;
336
337            if (localLOGV) {
338                Log.v(TAG, "Pointer " + pointer.id
339                    + ": Y velocity=" + accumX +" X velocity=" + accumY + " N=" + numTouches);
340            }
341        }
342    }
343
344    /**
345     * Retrieve the last computed X velocity.  You must first call
346     * {@link #computeCurrentVelocity(int)} before calling this function.
347     *
348     * @return The previously computed X velocity.
349     */
350    public float getXVelocity() {
351        Pointer pointer = getPointer(mActivePointerId);
352        return pointer != null ? pointer.xVelocity : 0;
353    }
354
355    /**
356     * Retrieve the last computed Y velocity.  You must first call
357     * {@link #computeCurrentVelocity(int)} before calling this function.
358     *
359     * @return The previously computed Y velocity.
360     */
361    public float getYVelocity() {
362        Pointer pointer = getPointer(mActivePointerId);
363        return pointer != null ? pointer.yVelocity : 0;
364    }
365
366    /**
367     * Retrieve the last computed X velocity.  You must first call
368     * {@link #computeCurrentVelocity(int)} before calling this function.
369     *
370     * @param id Which pointer's velocity to return.
371     * @return The previously computed X velocity.
372     */
373    public float getXVelocity(int id) {
374        Pointer pointer = getPointer(id);
375        return pointer != null ? pointer.xVelocity : 0;
376    }
377
378    /**
379     * Retrieve the last computed Y velocity.  You must first call
380     * {@link #computeCurrentVelocity(int)} before calling this function.
381     *
382     * @param id Which pointer's velocity to return.
383     * @return The previously computed Y velocity.
384     */
385    public float getYVelocity(int id) {
386        Pointer pointer = getPointer(id);
387        return pointer != null ? pointer.yVelocity : 0;
388    }
389
390    private final Pointer getPointer(int id) {
391        for (Pointer pointer = mPointerListHead; pointer != null; pointer = pointer.next) {
392            if (pointer.id == id) {
393                return pointer;
394            }
395        }
396        return null;
397    }
398
399    private static final Pointer obtainPointer() {
400        synchronized (sPool) {
401            if (sRecycledPointerCount != 0) {
402                Pointer element = sRecycledPointerListHead;
403                sRecycledPointerCount -= 1;
404                sRecycledPointerListHead = element.next;
405                element.next = null;
406                return element;
407            }
408        }
409        return new Pointer();
410    }
411
412    private static final void releasePointer(Pointer pointer) {
413        synchronized (sPool) {
414            if (sRecycledPointerCount < POINTER_POOL_CAPACITY) {
415                pointer.next = sRecycledPointerListHead;
416                sRecycledPointerCount += 1;
417                sRecycledPointerListHead = pointer;
418            }
419        }
420    }
421
422    private static final void releasePointerList(Pointer pointer) {
423        if (pointer != null) {
424            synchronized (sPool) {
425                int count = sRecycledPointerCount;
426                if (count >= POINTER_POOL_CAPACITY) {
427                    return;
428                }
429
430                Pointer tail = pointer;
431                for (;;) {
432                    count += 1;
433                    if (count >= POINTER_POOL_CAPACITY) {
434                        break;
435                    }
436
437                    Pointer next = tail.next;
438                    if (next == null) {
439                        break;
440                    }
441                    tail = next;
442                }
443
444                tail.next = sRecycledPointerListHead;
445                sRecycledPointerCount = count;
446                sRecycledPointerListHead = pointer;
447            }
448        }
449    }
450}
451