ScaleGestureDetector.java revision d0197f3669efda060c7ee2069ff41bd970fd6d9c
1ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell/*
2ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * Copyright (C) 2010 The Android Open Source Project
3ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell *
4ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * Licensed under the Apache License, Version 2.0 (the "License");
5ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * you may not use this file except in compliance with the License.
6ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * You may obtain a copy of the License at
7ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell *
8ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell *      http://www.apache.org/licenses/LICENSE-2.0
9ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell *
10ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * Unless required by applicable law or agreed to in writing, software
11ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * distributed under the License is distributed on an "AS IS" BASIS,
12ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * See the License for the specific language governing permissions and
14ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * limitations under the License.
15ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell */
16ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
17ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powellpackage android.view;
18ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
19ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powellimport android.content.Context;
20380b525220955ce4e4df8943b89082c7443ebfddAdam Powellimport android.util.DisplayMetrics;
21346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powellimport android.util.FloatMath;
220818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powellimport android.util.Log;
23ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
24ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell/**
25ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * Detects transformation gestures involving more than one pointer ("multitouch")
26ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * using the supplied {@link MotionEvent}s. The {@link OnScaleGestureListener}
27ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * callback will notify users when a particular gesture event has occurred.
28ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * This class should only be used with {@link MotionEvent}s reported via touch.
2947c41e807e36999e4d0d2072e41a82bc45655ff2Erik *
30ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * To use this class:
31ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * <ul>
32ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell *  <li>Create an instance of the {@code ScaleGestureDetector} for your
33ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell *      {@link View}
34ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell *  <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
35ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell *          {@link #onTouchEvent(MotionEvent)}. The methods defined in your
36ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell *          callback will be executed when the events occur.
37ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * </ul>
38ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell */
39ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powellpublic class ScaleGestureDetector {
400818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell    private static final String TAG = "ScaleGestureDetector";
410818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell
42ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
43ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * The listener for receiving notifications when gestures occur.
44ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * If you want to listen for all the different gestures then implement
45ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * this interface. If you only want to listen for a subset it might
46ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * be easier to extend {@link SimpleOnScaleGestureListener}.
4747c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
48ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * An application will receive events in the following order:
49ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * <ul>
50ab905c87b7324d15715b78eaae7ef8558ad3bd10Adam Powell     *  <li>One {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)}
51ab905c87b7324d15715b78eaae7ef8558ad3bd10Adam Powell     *  <li>Zero or more {@link OnScaleGestureListener#onScale(ScaleGestureDetector)}
52ab905c87b7324d15715b78eaae7ef8558ad3bd10Adam Powell     *  <li>One {@link OnScaleGestureListener#onScaleEnd(ScaleGestureDetector)}
53ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * </ul>
54ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
55ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public interface OnScaleGestureListener {
56ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        /**
57ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * Responds to scaling events for a gesture in progress.
58ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * Reported by pointer motion.
5947c41e807e36999e4d0d2072e41a82bc45655ff2Erik         *
60ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * @param detector The detector reporting the event - use this to
61ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          retrieve extended info about event state.
62ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * @return Whether or not the detector should consider this event
63ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          as handled. If an event was not handled, the detector
64ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          will continue to accumulate movement until an event is
65ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          handled. This can be useful if an application, for example,
66ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          only wants to update scaling factors if the change is
67ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          greater than 0.01.
68ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         */
69ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        public boolean onScale(ScaleGestureDetector detector);
70ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
71ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        /**
72ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * Responds to the beginning of a scaling gesture. Reported by
73ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * new pointers going down.
7447c41e807e36999e4d0d2072e41a82bc45655ff2Erik         *
75ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * @param detector The detector reporting the event - use this to
76ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          retrieve extended info about event state.
77ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * @return Whether or not the detector should continue recognizing
78ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          this gesture. For example, if a gesture is beginning
79ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          with a focal point outside of a region where it makes
80ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          sense, onScaleBegin() may return false to ignore the
81ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          rest of the gesture.
82ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         */
83ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        public boolean onScaleBegin(ScaleGestureDetector detector);
84ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
85ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        /**
86ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * Responds to the end of a scale gesture. Reported by existing
87216bccf804db9c972b317620a27de6a8adf7fbfeAdam Powell         * pointers going up.
8847c41e807e36999e4d0d2072e41a82bc45655ff2Erik         *
89ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()}
90ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * and {@link ScaleGestureDetector#getFocusY()} will return the location
91ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * of the pointer remaining on the screen.
9247c41e807e36999e4d0d2072e41a82bc45655ff2Erik         *
93ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * @param detector The detector reporting the event - use this to
94ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          retrieve extended info about event state.
95ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         */
96ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        public void onScaleEnd(ScaleGestureDetector detector);
97ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
9847c41e807e36999e4d0d2072e41a82bc45655ff2Erik
99ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
100ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * A convenience class to extend when you only want to listen for a subset
101ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * of scaling-related events. This implements all methods in
102ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * {@link OnScaleGestureListener} but does nothing.
103346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} returns
104346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * {@code false} so that a subclass can retrieve the accumulated scale
105346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * factor in an overridden onScaleEnd.
106346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} returns
10747c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * {@code true}.
108ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
109216bccf804db9c972b317620a27de6a8adf7fbfeAdam Powell    public static class SimpleOnScaleGestureListener implements OnScaleGestureListener {
110ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
111ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        public boolean onScale(ScaleGestureDetector detector) {
112346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell            return false;
113ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
114ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
115ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        public boolean onScaleBegin(ScaleGestureDetector detector) {
116ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            return true;
117ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
118ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
119ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        public void onScaleEnd(ScaleGestureDetector detector) {
120ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            // Intentionally empty
121ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
122ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
123ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
124346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell    /**
125346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * This value is the threshold ratio between our previous combined pressure
126346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * and the current combined pressure. We will only fire an onScale event if
127346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * the computed ratio between the current and previous event pressures is
128346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * greater than this value. When pressure decreases rapidly between events
129346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * the position values can often be imprecise, as it usually indicates
130346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * that the user is in the process of lifting a pointer off of the device.
131346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * Its value was tuned experimentally.
132346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     */
133ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private static final float PRESSURE_THRESHOLD = 0.67f;
134ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
135346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell    private final Context mContext;
136346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell    private final OnScaleGestureListener mListener;
137ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private boolean mGestureInProgress;
138ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
139ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private MotionEvent mPrevEvent;
140ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private MotionEvent mCurrEvent;
141ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
142ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mFocusX;
143ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mFocusY;
144ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mPrevFingerDiffX;
145ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mPrevFingerDiffY;
146ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mCurrFingerDiffX;
147ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mCurrFingerDiffY;
148ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mCurrLen;
149ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mPrevLen;
150ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mScaleFactor;
151ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mCurrPressure;
152ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mPrevPressure;
153ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private long mTimeDelta;
15447c41e807e36999e4d0d2072e41a82bc45655ff2Erik
155346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell    private final float mEdgeSlop;
156380b525220955ce4e4df8943b89082c7443ebfddAdam Powell    private float mRightSlopEdge;
157380b525220955ce4e4df8943b89082c7443ebfddAdam Powell    private float mBottomSlopEdge;
158380b525220955ce4e4df8943b89082c7443ebfddAdam Powell    private boolean mSloppyGesture;
159d0197f3669efda060c7ee2069ff41bd970fd6d9cAdam Powell    private boolean mInvalidGesture;
160ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
161e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell    // Pointer IDs currently responsible for the two fingers controlling the gesture
162e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell    private int mActiveId0;
163e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell    private int mActiveId1;
164e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell    private boolean mActive0MostRecent;
165e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
166ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
167380b525220955ce4e4df8943b89082c7443ebfddAdam Powell        ViewConfiguration config = ViewConfiguration.get(context);
168ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mContext = context;
169ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mListener = listener;
170380b525220955ce4e4df8943b89082c7443ebfddAdam Powell        mEdgeSlop = config.getScaledEdgeSlop();
171ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
172ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
173ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public boolean onTouchEvent(MotionEvent event) {
174e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final int action = event.getActionMasked();
175ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        boolean handled = true;
176ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
1770818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell        if (action == MotionEvent.ACTION_DOWN) {
1780818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell            reset(); // Start fresh
1790818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell        }
1800818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell
181d0197f3669efda060c7ee2069ff41bd970fd6d9cAdam Powell        if (mInvalidGesture) return false;
182d0197f3669efda060c7ee2069ff41bd970fd6d9cAdam Powell
183ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        if (!mGestureInProgress) {
184e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell            switch (action) {
185e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell            case MotionEvent.ACTION_DOWN: {
186e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                mActiveId0 = event.getPointerId(0);
187e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                mActive0MostRecent = true;
188e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell            }
189e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell            break;
190e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
191e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell            case MotionEvent.ACTION_UP:
192e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                reset();
193e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                break;
194e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
195f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell            case MotionEvent.ACTION_POINTER_DOWN: {
196ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                // We have a new multi-finger gesture
197380b525220955ce4e4df8943b89082c7443ebfddAdam Powell
198d5ada83c719d366d3063572ca6ce5ab8918fd39bGrace Kloba                // as orientation can change, query the metrics in touch down
199d5ada83c719d366d3063572ca6ce5ab8918fd39bGrace Kloba                DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
200d5ada83c719d366d3063572ca6ce5ab8918fd39bGrace Kloba                mRightSlopEdge = metrics.widthPixels - mEdgeSlop;
201d5ada83c719d366d3063572ca6ce5ab8918fd39bGrace Kloba                mBottomSlopEdge = metrics.heightPixels - mEdgeSlop;
202d5ada83c719d366d3063572ca6ce5ab8918fd39bGrace Kloba
203e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                if (mPrevEvent != null) mPrevEvent.recycle();
204ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                mPrevEvent = MotionEvent.obtain(event);
205ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                mTimeDelta = 0;
206380b525220955ce4e4df8943b89082c7443ebfddAdam Powell
207e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                int index1 = event.getActionIndex();
208e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                int index0 = event.findPointerIndex(mActiveId0);
209e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                mActiveId1 = event.getPointerId(index1);
2100818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell                if (index0 < 0 || index0 == index1) {
2110818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell                    // Probably someone sending us a broken event stream.
2120818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell                    index0 = findNewActiveIndex(event, index0 == index1 ? -1 : mActiveId1, index0);
2130818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell                    mActiveId0 = event.getPointerId(index0);
2140818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell                }
215e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                mActive0MostRecent = false;
216e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
217ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                setContext(event);
218380b525220955ce4e4df8943b89082c7443ebfddAdam Powell
219380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                // Check if we have a sloppy gesture. If so, delay
220380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                // the beginning of the gesture until we're sure that's
221380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                // what the user wanted. Sloppy gestures can happen if the
222380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                // edge of the user's hand is touching the screen, for example.
223380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                final float edgeSlop = mEdgeSlop;
224380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                final float rightSlop = mRightSlopEdge;
225380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                final float bottomSlop = mBottomSlopEdge;
226e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                float x0 = getRawX(event, index0);
227e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                float y0 = getRawY(event, index0);
228e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                float x1 = getRawX(event, index1);
229e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                float y1 = getRawY(event, index1);
230380b525220955ce4e4df8943b89082c7443ebfddAdam Powell
2318f9fbb0c45265a6f5da51cb3bc35362d8e5bc900Grace Kloba                boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop
2328f9fbb0c45265a6f5da51cb3bc35362d8e5bc900Grace Kloba                        || x0 > rightSlop || y0 > bottomSlop;
2338f9fbb0c45265a6f5da51cb3bc35362d8e5bc900Grace Kloba                boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop
2348f9fbb0c45265a6f5da51cb3bc35362d8e5bc900Grace Kloba                        || x1 > rightSlop || y1 > bottomSlop;
235380b525220955ce4e4df8943b89082c7443ebfddAdam Powell
236f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                if (p0sloppy && p1sloppy) {
2378f9fbb0c45265a6f5da51cb3bc35362d8e5bc900Grace Kloba                    mFocusX = -1;
2388f9fbb0c45265a6f5da51cb3bc35362d8e5bc900Grace Kloba                    mFocusY = -1;
2398f9fbb0c45265a6f5da51cb3bc35362d8e5bc900Grace Kloba                    mSloppyGesture = true;
2408f9fbb0c45265a6f5da51cb3bc35362d8e5bc900Grace Kloba                } else if (p0sloppy) {
241e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mFocusX = event.getX(index1);
242e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mFocusY = event.getY(index1);
243380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                    mSloppyGesture = true;
244380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                } else if (p1sloppy) {
245e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mFocusX = event.getX(index0);
246e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mFocusY = event.getY(index0);
247380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                    mSloppyGesture = true;
248380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                } else {
249e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mSloppyGesture = false;
250380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                    mGestureInProgress = mListener.onScaleBegin(this);
251380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                }
252f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell            }
253f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell            break;
25447c41e807e36999e4d0d2072e41a82bc45655ff2Erik
255f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell            case MotionEvent.ACTION_MOVE:
256f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                if (mSloppyGesture) {
257f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    // Initiate sloppy gestures if we've moved outside of the slop area.
258f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    final float edgeSlop = mEdgeSlop;
259f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    final float rightSlop = mRightSlopEdge;
260f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    final float bottomSlop = mBottomSlopEdge;
261e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    int index0 = event.findPointerIndex(mActiveId0);
262e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    int index1 = event.findPointerIndex(mActiveId1);
2630fe4a135b78e3ee993dd5111cc3e48a5b48952fbAdam Powell
264e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    float x0 = getRawX(event, index0);
265e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    float y0 = getRawY(event, index0);
266e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    float x1 = getRawX(event, index1);
267e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    float y1 = getRawY(event, index1);
268380b525220955ce4e4df8943b89082c7443ebfddAdam Powell
269f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop
270e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            || x0 > rightSlop || y0 > bottomSlop;
271f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop
272e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            || x1 > rightSlop || y1 > bottomSlop;
273e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
274e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    if (p0sloppy) {
275e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        // Do we have a different pointer that isn't sloppy?
276e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        int index = findNewActiveIndex(event, mActiveId1, index0);
277e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        if (index >= 0) {
278e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            index0 = index;
279e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            mActiveId0 = event.getPointerId(index);
280e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            x0 = getRawX(event, index);
281e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            y0 = getRawY(event, index);
282e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            p0sloppy = false;
283e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        }
284e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    }
285e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
286e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    if (p1sloppy) {
287e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        // Do we have a different pointer that isn't sloppy?
288e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        int index = findNewActiveIndex(event, mActiveId0, index1);
289e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        if (index >= 0) {
290e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            index1 = index;
291e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            mActiveId1 = event.getPointerId(index);
292e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            x1 = getRawX(event, index);
293e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            y1 = getRawY(event, index);
294e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            p1sloppy = false;
295e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        }
296e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    }
297380b525220955ce4e4df8943b89082c7443ebfddAdam Powell
298f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    if(p0sloppy && p1sloppy) {
299f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                        mFocusX = -1;
300f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                        mFocusY = -1;
301f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    } else if (p0sloppy) {
302e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mFocusX = event.getX(index1);
303e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mFocusY = event.getY(index1);
304f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    } else if (p1sloppy) {
305e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mFocusX = event.getX(index0);
306e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mFocusY = event.getY(index0);
307f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    } else {
308f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                        mSloppyGesture = false;
309f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                        mGestureInProgress = mListener.onScaleBegin(this);
310f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    }
311f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                }
312f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                break;
31347c41e807e36999e4d0d2072e41a82bc45655ff2Erik
314f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell            case MotionEvent.ACTION_POINTER_UP:
315f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                if (mSloppyGesture) {
316e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    final int pointerCount = event.getPointerCount();
317e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    final int actionIndex = event.getActionIndex();
318e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    final int actionId = event.getPointerId(actionIndex);
319e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
320e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    if (pointerCount > 2) {
321e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        if (actionId == mActiveId0) {
322e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            final int newIndex = findNewActiveIndex(event, mActiveId1, actionIndex);
323e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            if (newIndex >= 0) mActiveId0 = event.getPointerId(newIndex);
324e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        } else if (actionId == mActiveId1) {
325e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            final int newIndex = findNewActiveIndex(event, mActiveId0, actionIndex);
326e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            if (newIndex >= 0) mActiveId1 = event.getPointerId(newIndex);
327e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        }
328e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    } else {
329e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        // Set focus point to the remaining finger
330e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        final int index = event.findPointerIndex(actionId == mActiveId0 ?
331e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                                mActiveId1 : mActiveId0);
332e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mActiveId0 = event.getPointerId(index);
3330818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell
334e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mActive0MostRecent = true;
335e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mActiveId1 = -1;
336e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mFocusX = event.getX(index);
337e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mFocusY = event.getY(index);
338e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    }
339380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                }
340f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                break;
341ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            }
342ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        } else {
343ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            // Transform gesture in progress - attempt to handle it
344e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell            switch (action) {
345e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                case MotionEvent.ACTION_POINTER_DOWN: {
346e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    // End the old gesture and begin a new one with the most recent two fingers.
347e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mListener.onScaleEnd(this);
348e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    final int oldActive0 = mActiveId0;
349e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    final int oldActive1 = mActiveId1;
350e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    reset();
351e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
352e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mPrevEvent = MotionEvent.obtain(event);
353e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mActiveId0 = mActive0MostRecent ? oldActive0 : oldActive1;
354e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mActiveId1 = event.getPointerId(event.getActionIndex());
355e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mActive0MostRecent = false;
356e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
3570818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell                    int index0 = event.findPointerIndex(mActiveId0);
3580818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell                    if (index0 < 0 || mActiveId0 == mActiveId1) {
3590818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell                        // Probably someone sending us a broken event stream.
3600818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell                        Log.e(TAG, "Got " + MotionEvent.actionToString(action) +
3610818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell                                " with bad state while a gesture was in progress. " +
3620818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell                                "Did you forget to pass an event to " +
3630818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell                                "ScaleGestureDetector#onTouchEvent?");
3640818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell                        index0 = findNewActiveIndex(event,
3650818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell                                mActiveId0 == mActiveId1 ? -1 : mActiveId1, index0);
3660818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell                        mActiveId0 = event.getPointerId(index0);
3670818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell                    }
3680818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell
369ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                    setContext(event);
370380b525220955ce4e4df8943b89082c7443ebfddAdam Powell
371e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mGestureInProgress = mListener.onScaleBegin(this);
372e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                }
373e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                break;
374380b525220955ce4e4df8943b89082c7443ebfddAdam Powell
375e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                case MotionEvent.ACTION_POINTER_UP: {
376e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    final int pointerCount = event.getPointerCount();
377e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    final int actionIndex = event.getActionIndex();
378e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    final int actionId = event.getPointerId(actionIndex);
379e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
380e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    boolean gestureEnded = false;
381e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    if (pointerCount > 2) {
382e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        if (actionId == mActiveId0) {
383e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            final int newIndex = findNewActiveIndex(event, mActiveId1, actionIndex);
384e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            if (newIndex >= 0) {
3850fe4a135b78e3ee993dd5111cc3e48a5b48952fbAdam Powell                                mListener.onScaleEnd(this);
386e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                                mActiveId0 = event.getPointerId(newIndex);
3870fe4a135b78e3ee993dd5111cc3e48a5b48952fbAdam Powell                                mActive0MostRecent = true;
3880fe4a135b78e3ee993dd5111cc3e48a5b48952fbAdam Powell                                mPrevEvent = MotionEvent.obtain(event);
3890fe4a135b78e3ee993dd5111cc3e48a5b48952fbAdam Powell                                setContext(event);
3900fe4a135b78e3ee993dd5111cc3e48a5b48952fbAdam Powell                                mGestureInProgress = mListener.onScaleBegin(this);
391e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            } else {
392e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                                gestureEnded = true;
393e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            }
394e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        } else if (actionId == mActiveId1) {
395e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            final int newIndex = findNewActiveIndex(event, mActiveId0, actionIndex);
396e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            if (newIndex >= 0) {
3970fe4a135b78e3ee993dd5111cc3e48a5b48952fbAdam Powell                                mListener.onScaleEnd(this);
398e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                                mActiveId1 = event.getPointerId(newIndex);
3990fe4a135b78e3ee993dd5111cc3e48a5b48952fbAdam Powell                                mActive0MostRecent = false;
4000fe4a135b78e3ee993dd5111cc3e48a5b48952fbAdam Powell                                mPrevEvent = MotionEvent.obtain(event);
4010fe4a135b78e3ee993dd5111cc3e48a5b48952fbAdam Powell                                setContext(event);
4020fe4a135b78e3ee993dd5111cc3e48a5b48952fbAdam Powell                                mGestureInProgress = mListener.onScaleBegin(this);
403e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            } else {
404e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                                gestureEnded = true;
405e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            }
406e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        }
407e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mPrevEvent.recycle();
408e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mPrevEvent = MotionEvent.obtain(event);
409e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        setContext(event);
410e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    } else {
411e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        gestureEnded = true;
412380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                    }
413ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
414e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    if (gestureEnded) {
415e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        // Gesture ended
416e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        setContext(event);
417e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
418e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        // Set focus point to the remaining finger
419e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        final int activeId = actionId == mActiveId0 ? mActiveId1 : mActiveId0;
420e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        final int index = event.findPointerIndex(activeId);
421e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mFocusX = event.getX(index);
422e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mFocusY = event.getY(index);
423ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
424380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                        mListener.onScaleEnd(this);
425e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        reset();
426e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mActiveId0 = activeId;
427e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mActive0MostRecent = true;
428380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                    }
429e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                }
430e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                break;
431ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
432e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                case MotionEvent.ACTION_CANCEL:
433e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mListener.onScaleEnd(this);
434ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                    reset();
435ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                    break;
436ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
437e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                case MotionEvent.ACTION_UP:
438e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    reset();
439e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    break;
440e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
441e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                case MotionEvent.ACTION_MOVE: {
442ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                    setContext(event);
443ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
444ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                    // Only accept the event if our relative pressure is within
445ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                    // a certain limit - this can help filter shaky data as a
446ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                    // finger is lifted.
447ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                    if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
448ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                        final boolean updatePrevious = mListener.onScale(this);
449ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
450ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                        if (updatePrevious) {
451ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                            mPrevEvent.recycle();
452ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                            mPrevEvent = MotionEvent.obtain(event);
453ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                        }
454ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                    }
455e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                }
456e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                break;
457ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            }
458ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
459ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        return handled;
460ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
46147c41e807e36999e4d0d2072e41a82bc45655ff2Erik
462e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell    private int findNewActiveIndex(MotionEvent ev, int otherActiveId, int oldIndex) {
463e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final int pointerCount = ev.getPointerCount();
464e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
465e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        // It's ok if this isn't found and returns -1, it simply won't match.
466e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final int otherActiveIndex = ev.findPointerIndex(otherActiveId);
467e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        int newActiveIndex = -1;
468e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
469e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        // Pick a new id and update tracking state. Only pick pointers not on the slop edges.
470e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        for (int i = 0; i < pointerCount; i++) {
471e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell            if (i != oldIndex && i != otherActiveIndex) {
472e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                final float edgeSlop = mEdgeSlop;
473e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                final float rightSlop = mRightSlopEdge;
474e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                final float bottomSlop = mBottomSlopEdge;
475e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                float x = getRawX(ev, i);
476e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                float y = getRawY(ev, i);
477e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                if (x >= edgeSlop && y >= edgeSlop && x <= rightSlop && y <= bottomSlop) {
478e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    newActiveIndex = i;
479e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    break;
480e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                }
481e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell            }
482e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        }
483e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
484e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        return newActiveIndex;
485e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell    }
486e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
487380b525220955ce4e4df8943b89082c7443ebfddAdam Powell    /**
48847c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * MotionEvent has no getRawX(int) method; simulate it pending future API approval.
489380b525220955ce4e4df8943b89082c7443ebfddAdam Powell     */
490380b525220955ce4e4df8943b89082c7443ebfddAdam Powell    private static float getRawX(MotionEvent event, int pointerIndex) {
4910fe4a135b78e3ee993dd5111cc3e48a5b48952fbAdam Powell        if (pointerIndex < 0) return Float.MIN_VALUE;
492e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        if (pointerIndex == 0) return event.getRawX();
4939bccdb7d5c93e350337e707bc6edf3cd017b8f96Adam Powell        float offset = event.getRawX() - event.getX();
494380b525220955ce4e4df8943b89082c7443ebfddAdam Powell        return event.getX(pointerIndex) + offset;
495380b525220955ce4e4df8943b89082c7443ebfddAdam Powell    }
49647c41e807e36999e4d0d2072e41a82bc45655ff2Erik
497380b525220955ce4e4df8943b89082c7443ebfddAdam Powell    /**
49847c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * MotionEvent has no getRawY(int) method; simulate it pending future API approval.
499380b525220955ce4e4df8943b89082c7443ebfddAdam Powell     */
500380b525220955ce4e4df8943b89082c7443ebfddAdam Powell    private static float getRawY(MotionEvent event, int pointerIndex) {
5010fe4a135b78e3ee993dd5111cc3e48a5b48952fbAdam Powell        if (pointerIndex < 0) return Float.MIN_VALUE;
502e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        if (pointerIndex == 0) return event.getRawY();
5039bccdb7d5c93e350337e707bc6edf3cd017b8f96Adam Powell        float offset = event.getRawY() - event.getY();
504380b525220955ce4e4df8943b89082c7443ebfddAdam Powell        return event.getY(pointerIndex) + offset;
505380b525220955ce4e4df8943b89082c7443ebfddAdam Powell    }
506ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
507ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private void setContext(MotionEvent curr) {
508ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        if (mCurrEvent != null) {
509ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            mCurrEvent.recycle();
510ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
511ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mCurrEvent = MotionEvent.obtain(curr);
512ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
513ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mCurrLen = -1;
514ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mPrevLen = -1;
515ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mScaleFactor = -1;
516ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
517ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        final MotionEvent prev = mPrevEvent;
518ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
519e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final int prevIndex0 = prev.findPointerIndex(mActiveId0);
520e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final int prevIndex1 = prev.findPointerIndex(mActiveId1);
521e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final int currIndex0 = curr.findPointerIndex(mActiveId0);
522e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final int currIndex1 = curr.findPointerIndex(mActiveId1);
523e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
524d0197f3669efda060c7ee2069ff41bd970fd6d9cAdam Powell        if (prevIndex0 < 0 || prevIndex1 < 0 || currIndex0 < 0 || currIndex1 < 0) {
525d0197f3669efda060c7ee2069ff41bd970fd6d9cAdam Powell            mInvalidGesture = true;
526d0197f3669efda060c7ee2069ff41bd970fd6d9cAdam Powell            Log.e(TAG, "Invalid MotionEvent stream detected.", new Throwable());
527d0197f3669efda060c7ee2069ff41bd970fd6d9cAdam Powell            if (mGestureInProgress) {
528d0197f3669efda060c7ee2069ff41bd970fd6d9cAdam Powell                mListener.onScaleEnd(this);
529d0197f3669efda060c7ee2069ff41bd970fd6d9cAdam Powell            }
530d0197f3669efda060c7ee2069ff41bd970fd6d9cAdam Powell            return;
531d0197f3669efda060c7ee2069ff41bd970fd6d9cAdam Powell        }
532d0197f3669efda060c7ee2069ff41bd970fd6d9cAdam Powell
533e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final float px0 = prev.getX(prevIndex0);
534e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final float py0 = prev.getY(prevIndex0);
535e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final float px1 = prev.getX(prevIndex1);
536e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final float py1 = prev.getY(prevIndex1);
537e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final float cx0 = curr.getX(currIndex0);
538e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final float cy0 = curr.getY(currIndex0);
539e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final float cx1 = curr.getX(currIndex1);
540e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final float cy1 = curr.getY(currIndex1);
541ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
542ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        final float pvx = px1 - px0;
543ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        final float pvy = py1 - py0;
544ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        final float cvx = cx1 - cx0;
545ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        final float cvy = cy1 - cy0;
546ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mPrevFingerDiffX = pvx;
547ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mPrevFingerDiffY = pvy;
548ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mCurrFingerDiffX = cvx;
549ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mCurrFingerDiffY = cvy;
550ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
551ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mFocusX = cx0 + cvx * 0.5f;
552ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mFocusY = cy0 + cvy * 0.5f;
553ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mTimeDelta = curr.getEventTime() - prev.getEventTime();
554e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        mCurrPressure = curr.getPressure(currIndex0) + curr.getPressure(currIndex1);
555e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        mPrevPressure = prev.getPressure(prevIndex0) + prev.getPressure(prevIndex1);
556ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
557ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
558ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private void reset() {
559ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        if (mPrevEvent != null) {
560ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            mPrevEvent.recycle();
561ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            mPrevEvent = null;
562ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
563ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        if (mCurrEvent != null) {
564ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            mCurrEvent.recycle();
565ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            mCurrEvent = null;
566ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
567380b525220955ce4e4df8943b89082c7443ebfddAdam Powell        mSloppyGesture = false;
568380b525220955ce4e4df8943b89082c7443ebfddAdam Powell        mGestureInProgress = false;
569e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        mActiveId0 = -1;
570e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        mActiveId1 = -1;
571d0197f3669efda060c7ee2069ff41bd970fd6d9cAdam Powell        mInvalidGesture = false;
572ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
573ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
574ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
575ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Returns {@code true} if a two-finger scale gesture is in progress.
576ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return {@code true} if a scale gesture is in progress, {@code false} otherwise.
577ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
578ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public boolean isInProgress() {
579ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        return mGestureInProgress;
580ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
581ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
582ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
583ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Get the X coordinate of the current gesture's focal point.
584ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * If a gesture is in progress, the focal point is directly between
585ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * the two pointers forming the gesture.
586ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * If a gesture is ending, the focal point is the location of the
587ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * remaining pointer on the screen.
588ab905c87b7324d15715b78eaae7ef8558ad3bd10Adam Powell     * If {@link #isInProgress()} would return false, the result of this
589ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * function is undefined.
59047c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
591ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return X coordinate of the focal point in pixels.
592ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
593ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public float getFocusX() {
594ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        return mFocusX;
595ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
596ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
597ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
598ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Get the Y coordinate of the current gesture's focal point.
599ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * If a gesture is in progress, the focal point is directly between
600ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * the two pointers forming the gesture.
601ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * If a gesture is ending, the focal point is the location of the
602ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * remaining pointer on the screen.
603ab905c87b7324d15715b78eaae7ef8558ad3bd10Adam Powell     * If {@link #isInProgress()} would return false, the result of this
604ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * function is undefined.
60547c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
606ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return Y coordinate of the focal point in pixels.
607ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
608ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public float getFocusY() {
609ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        return mFocusY;
610ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
611ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
612ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
613ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Return the current distance between the two pointers forming the
614ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * gesture in progress.
61547c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
616ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return Distance between pointers in pixels.
617ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
618ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public float getCurrentSpan() {
619ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        if (mCurrLen == -1) {
620ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            final float cvx = mCurrFingerDiffX;
621ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            final float cvy = mCurrFingerDiffY;
622346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell            mCurrLen = FloatMath.sqrt(cvx*cvx + cvy*cvy);
623ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
624ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        return mCurrLen;
625ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
626ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
627ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
62847c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * Return the current x distance between the two pointers forming the
62947c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * gesture in progress.
63047c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
63147c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * @return Distance between pointers in pixels.
63247c41e807e36999e4d0d2072e41a82bc45655ff2Erik     */
63347c41e807e36999e4d0d2072e41a82bc45655ff2Erik    public float getCurrentSpanX() {
63447c41e807e36999e4d0d2072e41a82bc45655ff2Erik        return mCurrFingerDiffX;
63547c41e807e36999e4d0d2072e41a82bc45655ff2Erik    }
63647c41e807e36999e4d0d2072e41a82bc45655ff2Erik
63747c41e807e36999e4d0d2072e41a82bc45655ff2Erik    /**
63847c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * Return the current y distance between the two pointers forming the
63947c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * gesture in progress.
64047c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
64147c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * @return Distance between pointers in pixels.
64247c41e807e36999e4d0d2072e41a82bc45655ff2Erik     */
64347c41e807e36999e4d0d2072e41a82bc45655ff2Erik    public float getCurrentSpanY() {
64447c41e807e36999e4d0d2072e41a82bc45655ff2Erik        return mCurrFingerDiffY;
64547c41e807e36999e4d0d2072e41a82bc45655ff2Erik    }
64647c41e807e36999e4d0d2072e41a82bc45655ff2Erik
64747c41e807e36999e4d0d2072e41a82bc45655ff2Erik    /**
648ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Return the previous distance between the two pointers forming the
649ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * gesture in progress.
65047c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
651ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return Previous distance between pointers in pixels.
652ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
653ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public float getPreviousSpan() {
654ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        if (mPrevLen == -1) {
655ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            final float pvx = mPrevFingerDiffX;
656ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            final float pvy = mPrevFingerDiffY;
657346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell            mPrevLen = FloatMath.sqrt(pvx*pvx + pvy*pvy);
658ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
659ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        return mPrevLen;
660ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
661ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
662ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
66347c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * Return the previous x distance between the two pointers forming the
66447c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * gesture in progress.
66547c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
66647c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * @return Previous distance between pointers in pixels.
66747c41e807e36999e4d0d2072e41a82bc45655ff2Erik     */
66847c41e807e36999e4d0d2072e41a82bc45655ff2Erik    public float getPreviousSpanX() {
66947c41e807e36999e4d0d2072e41a82bc45655ff2Erik        return mPrevFingerDiffX;
67047c41e807e36999e4d0d2072e41a82bc45655ff2Erik    }
67147c41e807e36999e4d0d2072e41a82bc45655ff2Erik
67247c41e807e36999e4d0d2072e41a82bc45655ff2Erik    /**
67347c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * Return the previous y distance between the two pointers forming the
67447c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * gesture in progress.
67547c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
67647c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * @return Previous distance between pointers in pixels.
67747c41e807e36999e4d0d2072e41a82bc45655ff2Erik     */
67847c41e807e36999e4d0d2072e41a82bc45655ff2Erik    public float getPreviousSpanY() {
67947c41e807e36999e4d0d2072e41a82bc45655ff2Erik        return mPrevFingerDiffY;
68047c41e807e36999e4d0d2072e41a82bc45655ff2Erik    }
68147c41e807e36999e4d0d2072e41a82bc45655ff2Erik
68247c41e807e36999e4d0d2072e41a82bc45655ff2Erik    /**
683ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Return the scaling factor from the previous scale event to the current
684ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * event. This value is defined as
685ab905c87b7324d15715b78eaae7ef8558ad3bd10Adam Powell     * ({@link #getCurrentSpan()} / {@link #getPreviousSpan()}).
68647c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
687ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return The current scaling factor.
688ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
689ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public float getScaleFactor() {
690ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        if (mScaleFactor == -1) {
691ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            mScaleFactor = getCurrentSpan() / getPreviousSpan();
692ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
693ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        return mScaleFactor;
694ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
69547c41e807e36999e4d0d2072e41a82bc45655ff2Erik
696ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
697ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Return the time difference in milliseconds between the previous
698ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * accepted scaling event and the current scaling event.
69947c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
700ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return Time difference since the last scaling event in milliseconds.
701ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
702ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public long getTimeDelta() {
703ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        return mTimeDelta;
704ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
70547c41e807e36999e4d0d2072e41a82bc45655ff2Erik
706ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
707ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Return the event time of the current event being processed.
70847c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
709ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return Current event time in milliseconds.
710ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
711ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public long getEventTime() {
712ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        return mCurrEvent.getEventTime();
713ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
714ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell}
715