ScaleGestureDetector.java revision e33cef8037cb87386e17bcf8701a47452d262fa6
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;
22ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
23ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell/**
24ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * Detects transformation gestures involving more than one pointer ("multitouch")
25ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * using the supplied {@link MotionEvent}s. The {@link OnScaleGestureListener}
26ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * callback will notify users when a particular gesture event has occurred.
27ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * This class should only be used with {@link MotionEvent}s reported via touch.
2847c41e807e36999e4d0d2072e41a82bc45655ff2Erik *
29ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * To use this class:
30ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * <ul>
31ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell *  <li>Create an instance of the {@code ScaleGestureDetector} for your
32ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell *      {@link View}
33ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell *  <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
34ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell *          {@link #onTouchEvent(MotionEvent)}. The methods defined in your
35ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell *          callback will be executed when the events occur.
36ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * </ul>
37ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell */
38ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powellpublic class ScaleGestureDetector {
39ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
40ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * The listener for receiving notifications when gestures occur.
41ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * If you want to listen for all the different gestures then implement
42ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * this interface. If you only want to listen for a subset it might
43ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * be easier to extend {@link SimpleOnScaleGestureListener}.
4447c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
45ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * An application will receive events in the following order:
46ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * <ul>
47ab905c87b7324d15715b78eaae7ef8558ad3bd10Adam Powell     *  <li>One {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)}
48ab905c87b7324d15715b78eaae7ef8558ad3bd10Adam Powell     *  <li>Zero or more {@link OnScaleGestureListener#onScale(ScaleGestureDetector)}
49ab905c87b7324d15715b78eaae7ef8558ad3bd10Adam Powell     *  <li>One {@link OnScaleGestureListener#onScaleEnd(ScaleGestureDetector)}
50ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * </ul>
51ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
52ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public interface OnScaleGestureListener {
53ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        /**
54ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * Responds to scaling events for a gesture in progress.
55ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * Reported by pointer motion.
5647c41e807e36999e4d0d2072e41a82bc45655ff2Erik         *
57ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * @param detector The detector reporting the event - use this to
58ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          retrieve extended info about event state.
59ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * @return Whether or not the detector should consider this event
60ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          as handled. If an event was not handled, the detector
61ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          will continue to accumulate movement until an event is
62ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          handled. This can be useful if an application, for example,
63ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          only wants to update scaling factors if the change is
64ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          greater than 0.01.
65ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         */
66ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        public boolean onScale(ScaleGestureDetector detector);
67ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
68ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        /**
69ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * Responds to the beginning of a scaling gesture. Reported by
70ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * new pointers going down.
7147c41e807e36999e4d0d2072e41a82bc45655ff2Erik         *
72ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * @param detector The detector reporting the event - use this to
73ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          retrieve extended info about event state.
74ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * @return Whether or not the detector should continue recognizing
75ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          this gesture. For example, if a gesture is beginning
76ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          with a focal point outside of a region where it makes
77ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          sense, onScaleBegin() may return false to ignore the
78ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          rest of the gesture.
79ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         */
80ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        public boolean onScaleBegin(ScaleGestureDetector detector);
81ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
82ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        /**
83ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * Responds to the end of a scale gesture. Reported by existing
84216bccf804db9c972b317620a27de6a8adf7fbfeAdam Powell         * pointers going up.
8547c41e807e36999e4d0d2072e41a82bc45655ff2Erik         *
86ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()}
87ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * and {@link ScaleGestureDetector#getFocusY()} will return the location
88ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * of the pointer remaining on the screen.
8947c41e807e36999e4d0d2072e41a82bc45655ff2Erik         *
90ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * @param detector The detector reporting the event - use this to
91ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          retrieve extended info about event state.
92ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         */
93ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        public void onScaleEnd(ScaleGestureDetector detector);
94ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
9547c41e807e36999e4d0d2072e41a82bc45655ff2Erik
96ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
97ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * A convenience class to extend when you only want to listen for a subset
98ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * of scaling-related events. This implements all methods in
99ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * {@link OnScaleGestureListener} but does nothing.
100346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} returns
101346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * {@code false} so that a subclass can retrieve the accumulated scale
102346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * factor in an overridden onScaleEnd.
103346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} returns
10447c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * {@code true}.
105ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
106216bccf804db9c972b317620a27de6a8adf7fbfeAdam Powell    public static class SimpleOnScaleGestureListener implements OnScaleGestureListener {
107ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
108ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        public boolean onScale(ScaleGestureDetector detector) {
109346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell            return false;
110ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
111ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
112ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        public boolean onScaleBegin(ScaleGestureDetector detector) {
113ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            return true;
114ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
115ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
116ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        public void onScaleEnd(ScaleGestureDetector detector) {
117ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            // Intentionally empty
118ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
119ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
120ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
121346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell    /**
122346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * This value is the threshold ratio between our previous combined pressure
123346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * and the current combined pressure. We will only fire an onScale event if
124346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * the computed ratio between the current and previous event pressures is
125346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * greater than this value. When pressure decreases rapidly between events
126346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * the position values can often be imprecise, as it usually indicates
127346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * that the user is in the process of lifting a pointer off of the device.
128346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * Its value was tuned experimentally.
129346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     */
130ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private static final float PRESSURE_THRESHOLD = 0.67f;
131ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
132346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell    private final Context mContext;
133346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell    private final OnScaleGestureListener mListener;
134ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private boolean mGestureInProgress;
135ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
136ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private MotionEvent mPrevEvent;
137ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private MotionEvent mCurrEvent;
138ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
139ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mFocusX;
140ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mFocusY;
141ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mPrevFingerDiffX;
142ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mPrevFingerDiffY;
143ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mCurrFingerDiffX;
144ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mCurrFingerDiffY;
145ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mCurrLen;
146ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mPrevLen;
147ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mScaleFactor;
148ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mCurrPressure;
149ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mPrevPressure;
150ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private long mTimeDelta;
15147c41e807e36999e4d0d2072e41a82bc45655ff2Erik
152346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell    private final float mEdgeSlop;
153380b525220955ce4e4df8943b89082c7443ebfddAdam Powell    private float mRightSlopEdge;
154380b525220955ce4e4df8943b89082c7443ebfddAdam Powell    private float mBottomSlopEdge;
155380b525220955ce4e4df8943b89082c7443ebfddAdam Powell    private boolean mSloppyGesture;
156ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
157e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell    // Pointer IDs currently responsible for the two fingers controlling the gesture
158e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell    private int mActiveId0;
159e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell    private int mActiveId1;
160e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell    private boolean mActive0MostRecent;
161e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
162ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
163380b525220955ce4e4df8943b89082c7443ebfddAdam Powell        ViewConfiguration config = ViewConfiguration.get(context);
164ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mContext = context;
165ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mListener = listener;
166380b525220955ce4e4df8943b89082c7443ebfddAdam Powell        mEdgeSlop = config.getScaledEdgeSlop();
167ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
168ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
169ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public boolean onTouchEvent(MotionEvent event) {
170e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final int action = event.getActionMasked();
171ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        boolean handled = true;
172ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
173ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        if (!mGestureInProgress) {
174e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell            switch (action) {
175e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell            case MotionEvent.ACTION_DOWN: {
176e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                mActiveId0 = event.getPointerId(0);
177e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                mActive0MostRecent = true;
178e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell            }
179e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell            break;
180e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
181e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell            case MotionEvent.ACTION_UP:
182e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                reset();
183e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                break;
184e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
185f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell            case MotionEvent.ACTION_POINTER_DOWN: {
186ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                // We have a new multi-finger gesture
187380b525220955ce4e4df8943b89082c7443ebfddAdam Powell
188d5ada83c719d366d3063572ca6ce5ab8918fd39bGrace Kloba                // as orientation can change, query the metrics in touch down
189d5ada83c719d366d3063572ca6ce5ab8918fd39bGrace Kloba                DisplayMetrics metrics = mContext.getResources().getDisplayMetrics();
190d5ada83c719d366d3063572ca6ce5ab8918fd39bGrace Kloba                mRightSlopEdge = metrics.widthPixels - mEdgeSlop;
191d5ada83c719d366d3063572ca6ce5ab8918fd39bGrace Kloba                mBottomSlopEdge = metrics.heightPixels - mEdgeSlop;
192d5ada83c719d366d3063572ca6ce5ab8918fd39bGrace Kloba
193e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                if (mPrevEvent != null) mPrevEvent.recycle();
194ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                mPrevEvent = MotionEvent.obtain(event);
195ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                mTimeDelta = 0;
196380b525220955ce4e4df8943b89082c7443ebfddAdam Powell
197e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                int index1 = event.getActionIndex();
198e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                int index0 = event.findPointerIndex(mActiveId0);
199e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                mActiveId1 = event.getPointerId(index1);
200e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                mActive0MostRecent = false;
201e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
202ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                setContext(event);
203380b525220955ce4e4df8943b89082c7443ebfddAdam Powell
204380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                // Check if we have a sloppy gesture. If so, delay
205380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                // the beginning of the gesture until we're sure that's
206380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                // what the user wanted. Sloppy gestures can happen if the
207380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                // edge of the user's hand is touching the screen, for example.
208380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                final float edgeSlop = mEdgeSlop;
209380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                final float rightSlop = mRightSlopEdge;
210380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                final float bottomSlop = mBottomSlopEdge;
211e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                float x0 = getRawX(event, index0);
212e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                float y0 = getRawY(event, index0);
213e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                float x1 = getRawX(event, index1);
214e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                float y1 = getRawY(event, index1);
215380b525220955ce4e4df8943b89082c7443ebfddAdam Powell
2168f9fbb0c45265a6f5da51cb3bc35362d8e5bc900Grace Kloba                boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop
2178f9fbb0c45265a6f5da51cb3bc35362d8e5bc900Grace Kloba                        || x0 > rightSlop || y0 > bottomSlop;
2188f9fbb0c45265a6f5da51cb3bc35362d8e5bc900Grace Kloba                boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop
2198f9fbb0c45265a6f5da51cb3bc35362d8e5bc900Grace Kloba                        || x1 > rightSlop || y1 > bottomSlop;
220380b525220955ce4e4df8943b89082c7443ebfddAdam Powell
221f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                if (p0sloppy && p1sloppy) {
2228f9fbb0c45265a6f5da51cb3bc35362d8e5bc900Grace Kloba                    mFocusX = -1;
2238f9fbb0c45265a6f5da51cb3bc35362d8e5bc900Grace Kloba                    mFocusY = -1;
2248f9fbb0c45265a6f5da51cb3bc35362d8e5bc900Grace Kloba                    mSloppyGesture = true;
2258f9fbb0c45265a6f5da51cb3bc35362d8e5bc900Grace Kloba                } else if (p0sloppy) {
226e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mFocusX = event.getX(index1);
227e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mFocusY = event.getY(index1);
228380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                    mSloppyGesture = true;
229380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                } else if (p1sloppy) {
230e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mFocusX = event.getX(index0);
231e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mFocusY = event.getY(index0);
232380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                    mSloppyGesture = true;
233380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                } else {
234e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mSloppyGesture = false;
235380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                    mGestureInProgress = mListener.onScaleBegin(this);
236380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                }
237f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell            }
238f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell            break;
23947c41e807e36999e4d0d2072e41a82bc45655ff2Erik
240f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell            case MotionEvent.ACTION_MOVE:
241f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                if (mSloppyGesture) {
242f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    // Initiate sloppy gestures if we've moved outside of the slop area.
243f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    final float edgeSlop = mEdgeSlop;
244f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    final float rightSlop = mRightSlopEdge;
245f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    final float bottomSlop = mBottomSlopEdge;
246e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    int index0 = event.findPointerIndex(mActiveId0);
247e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    int index1 = event.findPointerIndex(mActiveId1);
248e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    float x0 = getRawX(event, index0);
249e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    float y0 = getRawY(event, index0);
250e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    float x1 = getRawX(event, index1);
251e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    float y1 = getRawY(event, index1);
252380b525220955ce4e4df8943b89082c7443ebfddAdam Powell
253f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    boolean p0sloppy = x0 < edgeSlop || y0 < edgeSlop
254e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            || x0 > rightSlop || y0 > bottomSlop;
255f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    boolean p1sloppy = x1 < edgeSlop || y1 < edgeSlop
256e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            || x1 > rightSlop || y1 > bottomSlop;
257e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
258e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    if (p0sloppy) {
259e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        // Do we have a different pointer that isn't sloppy?
260e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        int index = findNewActiveIndex(event, mActiveId1, index0);
261e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        if (index >= 0) {
262e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            index0 = index;
263e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            mActiveId0 = event.getPointerId(index);
264e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            x0 = getRawX(event, index);
265e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            y0 = getRawY(event, index);
266e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            p0sloppy = false;
267e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        }
268e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    }
269e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
270e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    if (p1sloppy) {
271e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        // Do we have a different pointer that isn't sloppy?
272e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        int index = findNewActiveIndex(event, mActiveId0, index1);
273e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        if (index >= 0) {
274e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            index1 = index;
275e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            mActiveId1 = event.getPointerId(index);
276e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            x1 = getRawX(event, index);
277e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            y1 = getRawY(event, index);
278e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            p1sloppy = false;
279e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        }
280e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    }
281380b525220955ce4e4df8943b89082c7443ebfddAdam Powell
282f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    if(p0sloppy && p1sloppy) {
283f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                        mFocusX = -1;
284f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                        mFocusY = -1;
285f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    } else if (p0sloppy) {
286e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mFocusX = event.getX(index1);
287e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mFocusY = event.getY(index1);
288f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    } else if (p1sloppy) {
289e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mFocusX = event.getX(index0);
290e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mFocusY = event.getY(index0);
291f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    } else {
292f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                        mSloppyGesture = false;
293f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                        mGestureInProgress = mListener.onScaleBegin(this);
294f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                    }
295f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                }
296f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                break;
29747c41e807e36999e4d0d2072e41a82bc45655ff2Erik
298f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell            case MotionEvent.ACTION_POINTER_UP:
299f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                if (mSloppyGesture) {
300e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    final int pointerCount = event.getPointerCount();
301e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    final int actionIndex = event.getActionIndex();
302e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    final int actionId = event.getPointerId(actionIndex);
303e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
304e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    if (pointerCount > 2) {
305e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        if (actionId == mActiveId0) {
306e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            final int newIndex = findNewActiveIndex(event, mActiveId1, actionIndex);
307e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            if (newIndex >= 0) mActiveId0 = event.getPointerId(newIndex);
308e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        } else if (actionId == mActiveId1) {
309e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            final int newIndex = findNewActiveIndex(event, mActiveId0, actionIndex);
310e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            if (newIndex >= 0) mActiveId1 = event.getPointerId(newIndex);
311e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        }
312e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    } else {
313e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        // Set focus point to the remaining finger
314e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        final int index = event.findPointerIndex(actionId == mActiveId0 ?
315e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                                mActiveId1 : mActiveId0);
316e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mActiveId0 = event.getPointerId(index);
317e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mActive0MostRecent = true;
318e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mActiveId1 = -1;
319e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mFocusX = event.getX(index);
320e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mFocusY = event.getY(index);
321e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    }
322380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                }
323f5bcc6addd9c1f8f6bb8b8626540d003b3f964faAdam Powell                break;
324ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            }
325ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        } else {
326ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            // Transform gesture in progress - attempt to handle it
327e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell            switch (action) {
328e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                case MotionEvent.ACTION_POINTER_DOWN: {
329e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    // End the old gesture and begin a new one with the most recent two fingers.
330e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mListener.onScaleEnd(this);
331e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    final int oldActive0 = mActiveId0;
332e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    final int oldActive1 = mActiveId1;
333e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    reset();
334e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
335e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mPrevEvent = MotionEvent.obtain(event);
336e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mActiveId0 = mActive0MostRecent ? oldActive0 : oldActive1;
337e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mActiveId1 = event.getPointerId(event.getActionIndex());
338e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mActive0MostRecent = false;
339e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
340ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                    setContext(event);
341380b525220955ce4e4df8943b89082c7443ebfddAdam Powell
342e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mGestureInProgress = mListener.onScaleBegin(this);
343e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                }
344e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                break;
345380b525220955ce4e4df8943b89082c7443ebfddAdam Powell
346e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                case MotionEvent.ACTION_POINTER_UP: {
347e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    final int pointerCount = event.getPointerCount();
348e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    final int actionIndex = event.getActionIndex();
349e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    final int actionId = event.getPointerId(actionIndex);
350e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
351e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    boolean gestureEnded = false;
352e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    if (pointerCount > 2) {
353e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        if (actionId == mActiveId0) {
354e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            final int newIndex = findNewActiveIndex(event, mActiveId1, actionIndex);
355e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            if (newIndex >= 0) {
356e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                                mActiveId0 = event.getPointerId(newIndex);
357e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            } else {
358e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                                gestureEnded = true;
359e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            }
360e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        } else if (actionId == mActiveId1) {
361e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            final int newIndex = findNewActiveIndex(event, mActiveId0, actionIndex);
362e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            if (newIndex >= 0) {
363e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                                mActiveId1 = event.getPointerId(newIndex);
364e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            } else {
365e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                                gestureEnded = true;
366e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                            }
367e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        }
368e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mPrevEvent.recycle();
369e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mPrevEvent = MotionEvent.obtain(event);
370e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        setContext(event);
371e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    } else {
372e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        gestureEnded = true;
373380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                    }
374ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
375e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    if (gestureEnded) {
376e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        // Gesture ended
377e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        setContext(event);
378e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
379e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        // Set focus point to the remaining finger
380e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        final int activeId = actionId == mActiveId0 ? mActiveId1 : mActiveId0;
381e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        final int index = event.findPointerIndex(activeId);
382e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mFocusX = event.getX(index);
383e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mFocusY = event.getY(index);
384ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
385380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                        mListener.onScaleEnd(this);
386e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        reset();
387e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mActiveId0 = activeId;
388e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                        mActive0MostRecent = true;
389380b525220955ce4e4df8943b89082c7443ebfddAdam Powell                    }
390e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                }
391e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                break;
392ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
393e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                case MotionEvent.ACTION_CANCEL:
394e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    mListener.onScaleEnd(this);
395ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                    reset();
396ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                    break;
397ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
398e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                case MotionEvent.ACTION_UP:
399e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    reset();
400e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    break;
401e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
402e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                case MotionEvent.ACTION_MOVE: {
403ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                    setContext(event);
404ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
405ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                    // Only accept the event if our relative pressure is within
406ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                    // a certain limit - this can help filter shaky data as a
407ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                    // finger is lifted.
408ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                    if (mCurrPressure / mPrevPressure > PRESSURE_THRESHOLD) {
409ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                        final boolean updatePrevious = mListener.onScale(this);
410ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
411ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                        if (updatePrevious) {
412ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                            mPrevEvent.recycle();
413ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                            mPrevEvent = MotionEvent.obtain(event);
414ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                        }
415ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell                    }
416e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                }
417e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                break;
418ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            }
419ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
420ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        return handled;
421ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
42247c41e807e36999e4d0d2072e41a82bc45655ff2Erik
423e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell    private int findNewActiveIndex(MotionEvent ev, int otherActiveId, int oldIndex) {
424e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final int pointerCount = ev.getPointerCount();
425e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
426e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        // It's ok if this isn't found and returns -1, it simply won't match.
427e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final int otherActiveIndex = ev.findPointerIndex(otherActiveId);
428e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        int newActiveIndex = -1;
429e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
430e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        // Pick a new id and update tracking state. Only pick pointers not on the slop edges.
431e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        for (int i = 0; i < pointerCount; i++) {
432e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell            if (i != oldIndex && i != otherActiveIndex) {
433e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                final float edgeSlop = mEdgeSlop;
434e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                final float rightSlop = mRightSlopEdge;
435e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                final float bottomSlop = mBottomSlopEdge;
436e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                float x = getRawX(ev, i);
437e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                float y = getRawY(ev, i);
438e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                if (x >= edgeSlop && y >= edgeSlop && x <= rightSlop && y <= bottomSlop) {
439e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    newActiveIndex = i;
440e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                    break;
441e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell                }
442e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell            }
443e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        }
444e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
445e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        return newActiveIndex;
446e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell    }
447e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
448380b525220955ce4e4df8943b89082c7443ebfddAdam Powell    /**
44947c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * MotionEvent has no getRawX(int) method; simulate it pending future API approval.
450380b525220955ce4e4df8943b89082c7443ebfddAdam Powell     */
451380b525220955ce4e4df8943b89082c7443ebfddAdam Powell    private static float getRawX(MotionEvent event, int pointerIndex) {
452e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        if (pointerIndex == 0) return event.getRawX();
4539bccdb7d5c93e350337e707bc6edf3cd017b8f96Adam Powell        float offset = event.getRawX() - event.getX();
454380b525220955ce4e4df8943b89082c7443ebfddAdam Powell        return event.getX(pointerIndex) + offset;
455380b525220955ce4e4df8943b89082c7443ebfddAdam Powell    }
45647c41e807e36999e4d0d2072e41a82bc45655ff2Erik
457380b525220955ce4e4df8943b89082c7443ebfddAdam Powell    /**
45847c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * MotionEvent has no getRawY(int) method; simulate it pending future API approval.
459380b525220955ce4e4df8943b89082c7443ebfddAdam Powell     */
460380b525220955ce4e4df8943b89082c7443ebfddAdam Powell    private static float getRawY(MotionEvent event, int pointerIndex) {
461e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        if (pointerIndex == 0) return event.getRawY();
4629bccdb7d5c93e350337e707bc6edf3cd017b8f96Adam Powell        float offset = event.getRawY() - event.getY();
463380b525220955ce4e4df8943b89082c7443ebfddAdam Powell        return event.getY(pointerIndex) + offset;
464380b525220955ce4e4df8943b89082c7443ebfddAdam Powell    }
465ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
466ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private void setContext(MotionEvent curr) {
467ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        if (mCurrEvent != null) {
468ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            mCurrEvent.recycle();
469ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
470ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mCurrEvent = MotionEvent.obtain(curr);
471ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
472ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mCurrLen = -1;
473ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mPrevLen = -1;
474ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mScaleFactor = -1;
475ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
476ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        final MotionEvent prev = mPrevEvent;
477ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
478e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final int prevIndex0 = prev.findPointerIndex(mActiveId0);
479e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final int prevIndex1 = prev.findPointerIndex(mActiveId1);
480e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final int currIndex0 = curr.findPointerIndex(mActiveId0);
481e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final int currIndex1 = curr.findPointerIndex(mActiveId1);
482e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
483e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final float px0 = prev.getX(prevIndex0);
484e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final float py0 = prev.getY(prevIndex0);
485e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final float px1 = prev.getX(prevIndex1);
486e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final float py1 = prev.getY(prevIndex1);
487e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final float cx0 = curr.getX(currIndex0);
488e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final float cy0 = curr.getY(currIndex0);
489e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final float cx1 = curr.getX(currIndex1);
490e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final float cy1 = curr.getY(currIndex1);
491ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
492ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        final float pvx = px1 - px0;
493ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        final float pvy = py1 - py0;
494ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        final float cvx = cx1 - cx0;
495ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        final float cvy = cy1 - cy0;
496ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mPrevFingerDiffX = pvx;
497ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mPrevFingerDiffY = pvy;
498ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mCurrFingerDiffX = cvx;
499ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mCurrFingerDiffY = cvy;
500ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
501ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mFocusX = cx0 + cvx * 0.5f;
502ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mFocusY = cy0 + cvy * 0.5f;
503ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mTimeDelta = curr.getEventTime() - prev.getEventTime();
504e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        mCurrPressure = curr.getPressure(currIndex0) + curr.getPressure(currIndex1);
505e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        mPrevPressure = prev.getPressure(prevIndex0) + prev.getPressure(prevIndex1);
506ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
507ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
508ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private void reset() {
509ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        if (mPrevEvent != null) {
510ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            mPrevEvent.recycle();
511ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            mPrevEvent = null;
512ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
513ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        if (mCurrEvent != null) {
514ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            mCurrEvent.recycle();
515ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            mCurrEvent = null;
516ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
517380b525220955ce4e4df8943b89082c7443ebfddAdam Powell        mSloppyGesture = false;
518380b525220955ce4e4df8943b89082c7443ebfddAdam Powell        mGestureInProgress = false;
519e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        mActiveId0 = -1;
520e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        mActiveId1 = -1;
521ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
522ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
523ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
524ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Returns {@code true} if a two-finger scale gesture is in progress.
525ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return {@code true} if a scale gesture is in progress, {@code false} otherwise.
526ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
527ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public boolean isInProgress() {
528ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        return mGestureInProgress;
529ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
530ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
531ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
532ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Get the X coordinate of the current gesture's focal point.
533ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * If a gesture is in progress, the focal point is directly between
534ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * the two pointers forming the gesture.
535ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * If a gesture is ending, the focal point is the location of the
536ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * remaining pointer on the screen.
537ab905c87b7324d15715b78eaae7ef8558ad3bd10Adam Powell     * If {@link #isInProgress()} would return false, the result of this
538ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * function is undefined.
53947c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
540ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return X coordinate of the focal point in pixels.
541ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
542ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public float getFocusX() {
543ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        return mFocusX;
544ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
545ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
546ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
547ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Get the Y coordinate of the current gesture's focal point.
548ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * If a gesture is in progress, the focal point is directly between
549ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * the two pointers forming the gesture.
550ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * If a gesture is ending, the focal point is the location of the
551ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * remaining pointer on the screen.
552ab905c87b7324d15715b78eaae7ef8558ad3bd10Adam Powell     * If {@link #isInProgress()} would return false, the result of this
553ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * function is undefined.
55447c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
555ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return Y coordinate of the focal point in pixels.
556ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
557ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public float getFocusY() {
558ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        return mFocusY;
559ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
560ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
561ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
562ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Return the current distance between the two pointers forming the
563ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * gesture in progress.
56447c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
565ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return Distance between pointers in pixels.
566ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
567ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public float getCurrentSpan() {
568ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        if (mCurrLen == -1) {
569ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            final float cvx = mCurrFingerDiffX;
570ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            final float cvy = mCurrFingerDiffY;
571346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell            mCurrLen = FloatMath.sqrt(cvx*cvx + cvy*cvy);
572ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
573ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        return mCurrLen;
574ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
575ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
576ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
57747c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * Return the current x distance between the two pointers forming the
57847c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * gesture in progress.
57947c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
58047c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * @return Distance between pointers in pixels.
58147c41e807e36999e4d0d2072e41a82bc45655ff2Erik     */
58247c41e807e36999e4d0d2072e41a82bc45655ff2Erik    public float getCurrentSpanX() {
58347c41e807e36999e4d0d2072e41a82bc45655ff2Erik        return mCurrFingerDiffX;
58447c41e807e36999e4d0d2072e41a82bc45655ff2Erik    }
58547c41e807e36999e4d0d2072e41a82bc45655ff2Erik
58647c41e807e36999e4d0d2072e41a82bc45655ff2Erik    /**
58747c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * Return the current y distance between the two pointers forming the
58847c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * gesture in progress.
58947c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
59047c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * @return Distance between pointers in pixels.
59147c41e807e36999e4d0d2072e41a82bc45655ff2Erik     */
59247c41e807e36999e4d0d2072e41a82bc45655ff2Erik    public float getCurrentSpanY() {
59347c41e807e36999e4d0d2072e41a82bc45655ff2Erik        return mCurrFingerDiffY;
59447c41e807e36999e4d0d2072e41a82bc45655ff2Erik    }
59547c41e807e36999e4d0d2072e41a82bc45655ff2Erik
59647c41e807e36999e4d0d2072e41a82bc45655ff2Erik    /**
597ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Return the previous distance between the two pointers forming the
598ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * gesture in progress.
59947c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
600ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return Previous distance between pointers in pixels.
601ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
602ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public float getPreviousSpan() {
603ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        if (mPrevLen == -1) {
604ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            final float pvx = mPrevFingerDiffX;
605ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            final float pvy = mPrevFingerDiffY;
606346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell            mPrevLen = FloatMath.sqrt(pvx*pvx + pvy*pvy);
607ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
608ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        return mPrevLen;
609ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
610ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
611ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
61247c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * Return the previous x distance between the two pointers forming the
61347c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * gesture in progress.
61447c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
61547c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * @return Previous distance between pointers in pixels.
61647c41e807e36999e4d0d2072e41a82bc45655ff2Erik     */
61747c41e807e36999e4d0d2072e41a82bc45655ff2Erik    public float getPreviousSpanX() {
61847c41e807e36999e4d0d2072e41a82bc45655ff2Erik        return mPrevFingerDiffX;
61947c41e807e36999e4d0d2072e41a82bc45655ff2Erik    }
62047c41e807e36999e4d0d2072e41a82bc45655ff2Erik
62147c41e807e36999e4d0d2072e41a82bc45655ff2Erik    /**
62247c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * Return the previous y distance between the two pointers forming the
62347c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * gesture in progress.
62447c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
62547c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * @return Previous distance between pointers in pixels.
62647c41e807e36999e4d0d2072e41a82bc45655ff2Erik     */
62747c41e807e36999e4d0d2072e41a82bc45655ff2Erik    public float getPreviousSpanY() {
62847c41e807e36999e4d0d2072e41a82bc45655ff2Erik        return mPrevFingerDiffY;
62947c41e807e36999e4d0d2072e41a82bc45655ff2Erik    }
63047c41e807e36999e4d0d2072e41a82bc45655ff2Erik
63147c41e807e36999e4d0d2072e41a82bc45655ff2Erik    /**
632ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Return the scaling factor from the previous scale event to the current
633ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * event. This value is defined as
634ab905c87b7324d15715b78eaae7ef8558ad3bd10Adam Powell     * ({@link #getCurrentSpan()} / {@link #getPreviousSpan()}).
63547c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
636ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return The current scaling factor.
637ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
638ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public float getScaleFactor() {
639ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        if (mScaleFactor == -1) {
640ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            mScaleFactor = getCurrentSpan() / getPreviousSpan();
641ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
642ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        return mScaleFactor;
643ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
64447c41e807e36999e4d0d2072e41a82bc45655ff2Erik
645ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
646ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Return the time difference in milliseconds between the previous
647ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * accepted scaling event and the current scaling event.
64847c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
649ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return Time difference since the last scaling event in milliseconds.
650ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
651ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public long getTimeDelta() {
652ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        return mTimeDelta;
653ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
65447c41e807e36999e4d0d2072e41a82bc45655ff2Erik
655ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
656ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Return the event time of the current event being processed.
65747c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
658ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return Current event time in milliseconds.
659ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
660ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public long getEventTime() {
661ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        return mCurrEvent.getEventTime();
662ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
663ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell}
664