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