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;
203307958c6b4b3707c8861db829893b1f5820b677Adam Powellimport android.content.res.Resources;
21e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereiraimport android.os.Build;
22e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereiraimport android.os.Handler;
23a4ce6ae0d379c8866697d064cfd1661ea156405eAdam Powell
24ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell/**
25618cbea4e746196cbde43746706bec02e14b487bAdam Powell * Detects scaling transformation gestures using the supplied {@link MotionEvent}s.
26618cbea4e746196cbde43746706bec02e14b487bAdam Powell * The {@link OnScaleGestureListener} callback will notify users when a particular
27618cbea4e746196cbde43746706bec02e14b487bAdam Powell * gesture event has occurred.
28618cbea4e746196cbde43746706bec02e14b487bAdam Powell *
29ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * This class should only be used with {@link MotionEvent}s reported via touch.
3047c41e807e36999e4d0d2072e41a82bc45655ff2Erik *
31ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * To use this class:
32ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * <ul>
33ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell *  <li>Create an instance of the {@code ScaleGestureDetector} for your
34ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell *      {@link View}
35ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell *  <li>In the {@link View#onTouchEvent(MotionEvent)} method ensure you call
36ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell *          {@link #onTouchEvent(MotionEvent)}. The methods defined in your
37ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell *          callback will be executed when the events occur.
38ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell * </ul>
39ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell */
40ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powellpublic class ScaleGestureDetector {
410818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell    private static final String TAG = "ScaleGestureDetector";
420818020d7cb04d83d51b71b8262d34bd79a76a95Adam Powell
43ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
44ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * The listener for receiving notifications when gestures occur.
45ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * If you want to listen for all the different gestures then implement
46ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * this interface. If you only want to listen for a subset it might
47ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * be easier to extend {@link SimpleOnScaleGestureListener}.
4847c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
49ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * An application will receive events in the following order:
50ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * <ul>
51ab905c87b7324d15715b78eaae7ef8558ad3bd10Adam Powell     *  <li>One {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)}
52ab905c87b7324d15715b78eaae7ef8558ad3bd10Adam Powell     *  <li>Zero or more {@link OnScaleGestureListener#onScale(ScaleGestureDetector)}
53ab905c87b7324d15715b78eaae7ef8558ad3bd10Adam Powell     *  <li>One {@link OnScaleGestureListener#onScaleEnd(ScaleGestureDetector)}
54ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * </ul>
55ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
56ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public interface OnScaleGestureListener {
57ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        /**
58ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * Responds to scaling events for a gesture in progress.
59ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * Reported by pointer motion.
6047c41e807e36999e4d0d2072e41a82bc45655ff2Erik         *
61ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * @param detector The detector reporting the event - use this to
62ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          retrieve extended info about event state.
63ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * @return Whether or not the detector should consider this event
64ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          as handled. If an event was not handled, the detector
65ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          will continue to accumulate movement until an event is
66ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          handled. This can be useful if an application, for example,
67ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          only wants to update scaling factors if the change is
68ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          greater than 0.01.
69ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         */
70ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        public boolean onScale(ScaleGestureDetector detector);
71ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
72ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        /**
73ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * Responds to the beginning of a scaling gesture. Reported by
74ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * new pointers going down.
7547c41e807e36999e4d0d2072e41a82bc45655ff2Erik         *
76ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * @param detector The detector reporting the event - use this to
77ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          retrieve extended info about event state.
78ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * @return Whether or not the detector should continue recognizing
79ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          this gesture. For example, if a gesture is beginning
80ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          with a focal point outside of a region where it makes
81ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          sense, onScaleBegin() may return false to ignore the
82ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          rest of the gesture.
83ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         */
84ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        public boolean onScaleBegin(ScaleGestureDetector detector);
85ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
86ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        /**
87ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * Responds to the end of a scale gesture. Reported by existing
88216bccf804db9c972b317620a27de6a8adf7fbfeAdam Powell         * pointers going up.
8947c41e807e36999e4d0d2072e41a82bc45655ff2Erik         *
90ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()}
9147ec2fb37046797bebc82b505a13c552021b9ff3Adam Powell         * and {@link ScaleGestureDetector#getFocusY()} will return focal point
9247ec2fb37046797bebc82b505a13c552021b9ff3Adam Powell         * of the pointers remaining on the screen.
9347c41e807e36999e4d0d2072e41a82bc45655ff2Erik         *
94ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         * @param detector The detector reporting the event - use this to
95ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         *          retrieve extended info about event state.
96ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell         */
97ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        public void onScaleEnd(ScaleGestureDetector detector);
98ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
9947c41e807e36999e4d0d2072e41a82bc45655ff2Erik
100ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
101ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * A convenience class to extend when you only want to listen for a subset
102ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * of scaling-related events. This implements all methods in
103ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * {@link OnScaleGestureListener} but does nothing.
104346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * {@link OnScaleGestureListener#onScale(ScaleGestureDetector)} returns
105346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * {@code false} so that a subclass can retrieve the accumulated scale
106346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * factor in an overridden onScaleEnd.
107346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell     * {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)} returns
10847c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * {@code true}.
109ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
110216bccf804db9c972b317620a27de6a8adf7fbfeAdam Powell    public static class SimpleOnScaleGestureListener implements OnScaleGestureListener {
111ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
112ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        public boolean onScale(ScaleGestureDetector detector) {
113346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell            return false;
114ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
115ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
116ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        public boolean onScaleBegin(ScaleGestureDetector detector) {
117ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            return true;
118ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
119ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
120ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        public void onScaleEnd(ScaleGestureDetector detector) {
121ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            // Intentionally empty
122ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
123ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
124ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
125346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell    private final Context mContext;
126346c8fb036b99a33756c66dcebeb5d0f67a1df72Adam Powell    private final OnScaleGestureListener mListener;
127ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
128ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mFocusX;
129ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    private float mFocusY;
130618cbea4e746196cbde43746706bec02e14b487bAdam Powell
1319f1221f87e4762457c8fa0b4c0e5a291d9aef5c9Mindy Pereira    private boolean mQuickScaleEnabled;
1326895518d48f265371c26ab4c0d300c3313c4c210Mady Mellor    private boolean mStylusScaleEnabled;
133e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira
134618cbea4e746196cbde43746706bec02e14b487bAdam Powell    private float mCurrSpan;
135618cbea4e746196cbde43746706bec02e14b487bAdam Powell    private float mPrevSpan;
13647ec2fb37046797bebc82b505a13c552021b9ff3Adam Powell    private float mInitialSpan;
137618cbea4e746196cbde43746706bec02e14b487bAdam Powell    private float mCurrSpanX;
138618cbea4e746196cbde43746706bec02e14b487bAdam Powell    private float mCurrSpanY;
139618cbea4e746196cbde43746706bec02e14b487bAdam Powell    private float mPrevSpanX;
140618cbea4e746196cbde43746706bec02e14b487bAdam Powell    private float mPrevSpanY;
141618cbea4e746196cbde43746706bec02e14b487bAdam Powell    private long mCurrTime;
142618cbea4e746196cbde43746706bec02e14b487bAdam Powell    private long mPrevTime;
143618cbea4e746196cbde43746706bec02e14b487bAdam Powell    private boolean mInProgress;
14447ec2fb37046797bebc82b505a13c552021b9ff3Adam Powell    private int mSpanSlop;
145828e56ea6e8f4d5aa650052580f1f7873ad8296dAdam Powell    private int mMinSpan;
146e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
147e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira    private final Handler mHandler;
148a4ce6ae0d379c8866697d064cfd1661ea156405eAdam Powell
149847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor    private float mAnchoredScaleStartX;
150847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor    private float mAnchoredScaleStartY;
151847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor    private int mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
152847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor
153d736d2069b94348b519089348f4a85eb482db668Adam Powell    private static final long TOUCH_STABILIZE_TIME = 128; // ms
15424870ce4f222fec664a24e47cf6b12db36dbdff2Mindy Pereira    private static final float SCALE_FACTOR = .5f;
155847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor    private static final int ANCHORED_SCALE_MODE_NONE = 0;
156847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor    private static final int ANCHORED_SCALE_MODE_DOUBLE_TAP = 1;
1576895518d48f265371c26ab4c0d300c3313c4c210Mady Mellor    private static final int ANCHORED_SCALE_MODE_STYLUS = 2;
158e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira
159a4ce6ae0d379c8866697d064cfd1661ea156405eAdam Powell
16021bc5c917d4ee2a9b2b8173091e6bba85eaff899Jeff Brown    /**
16121bc5c917d4ee2a9b2b8173091e6bba85eaff899Jeff Brown     * Consistency verifier for debugging purposes.
16221bc5c917d4ee2a9b2b8173091e6bba85eaff899Jeff Brown     */
16321bc5c917d4ee2a9b2b8173091e6bba85eaff899Jeff Brown    private final InputEventConsistencyVerifier mInputEventConsistencyVerifier =
16421bc5c917d4ee2a9b2b8173091e6bba85eaff899Jeff Brown            InputEventConsistencyVerifier.isInstrumentationEnabled() ?
16521bc5c917d4ee2a9b2b8173091e6bba85eaff899Jeff Brown                    new InputEventConsistencyVerifier(this, 0) : null;
166e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira    private GestureDetector mGestureDetector;
167e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira
168e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira    private boolean mEventBeforeOrAboveStartingGestureEvent;
16921bc5c917d4ee2a9b2b8173091e6bba85eaff899Jeff Brown
170e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira    /**
171e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     * Creates a ScaleGestureDetector with the supplied listener.
172e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     * You may only use this constructor from a {@link android.os.Looper Looper} thread.
173e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     *
174e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     * @param context the application's context
175e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     * @param listener the listener invoked for all the callbacks, this must
176e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     * not be null.
177e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     *
178e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     * @throws NullPointerException if {@code listener} is null.
179e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     */
180ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public ScaleGestureDetector(Context context, OnScaleGestureListener listener) {
181e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira        this(context, listener, null);
182e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira    }
183e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira
184e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira    /**
185e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     * Creates a ScaleGestureDetector with the supplied listener.
186e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     * @see android.os.Handler#Handler()
187e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     *
188e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     * @param context the application's context
189e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     * @param listener the listener invoked for all the callbacks, this must
190e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     * not be null.
191e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     * @param handler the handler to use for running deferred listener events.
192e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     *
193e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     * @throws NullPointerException if {@code listener} is null.
194e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     */
195e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira    public ScaleGestureDetector(Context context, OnScaleGestureListener listener,
19624870ce4f222fec664a24e47cf6b12db36dbdff2Mindy Pereira                                Handler handler) {
197ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mContext = context;
198ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        mListener = listener;
19947ec2fb37046797bebc82b505a13c552021b9ff3Adam Powell        mSpanSlop = ViewConfiguration.get(context).getScaledTouchSlop() * 2;
2003307958c6b4b3707c8861db829893b1f5820b677Adam Powell
2013307958c6b4b3707c8861db829893b1f5820b677Adam Powell        final Resources res = context.getResources();
202e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira        mMinSpan = res.getDimensionPixelSize(com.android.internal.R.dimen.config_minScalingSpan);
203e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira        mHandler = handler;
204e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira        // Quick scale is enabled by default after JB_MR2
2057c36a685fea4ee2c93d1dad941061ed3b7c0c566Mady Mellor        final int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
2067c36a685fea4ee2c93d1dad941061ed3b7c0c566Mady Mellor        if (targetSdkVersion > Build.VERSION_CODES.JELLY_BEAN_MR2) {
207e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira            setQuickScaleEnabled(true);
208e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira        }
2097c36a685fea4ee2c93d1dad941061ed3b7c0c566Mady Mellor        // Stylus scale is enabled by default after LOLLIPOP_MR1
2107c36a685fea4ee2c93d1dad941061ed3b7c0c566Mady Mellor        if (targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
2117c36a685fea4ee2c93d1dad941061ed3b7c0c566Mady Mellor            setStylusScaleEnabled(true);
2127c36a685fea4ee2c93d1dad941061ed3b7c0c566Mady Mellor        }
213ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
214ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
215618cbea4e746196cbde43746706bec02e14b487bAdam Powell    /**
216618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener}
217618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * when appropriate.
218618cbea4e746196cbde43746706bec02e14b487bAdam Powell     *
219618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * <p>Applications should pass a complete and consistent event stream to this method.
220618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * A complete and consistent event stream involves all MotionEvents from the initial
221618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * ACTION_DOWN to the final ACTION_UP or ACTION_CANCEL.</p>
222618cbea4e746196cbde43746706bec02e14b487bAdam Powell     *
223618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * @param event The event to process
224618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * @return true if the event was processed and the detector wants to receive the
225618cbea4e746196cbde43746706bec02e14b487bAdam Powell     *         rest of the MotionEvents in this event stream.
226618cbea4e746196cbde43746706bec02e14b487bAdam Powell     */
227ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public boolean onTouchEvent(MotionEvent event) {
22821bc5c917d4ee2a9b2b8173091e6bba85eaff899Jeff Brown        if (mInputEventConsistencyVerifier != null) {
22921bc5c917d4ee2a9b2b8173091e6bba85eaff899Jeff Brown            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
23021bc5c917d4ee2a9b2b8173091e6bba85eaff899Jeff Brown        }
23121bc5c917d4ee2a9b2b8173091e6bba85eaff899Jeff Brown
2327232b0ad675402222bce3b478dbb682848d5e9e6Adam Powell        mCurrTime = event.getEventTime();
2337232b0ad675402222bce3b478dbb682848d5e9e6Adam Powell
234e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        final int action = event.getActionMasked();
235ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
236e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira        // Forward the event to check for double tap gesture
2379f1221f87e4762457c8fa0b4c0e5a291d9aef5c9Mindy Pereira        if (mQuickScaleEnabled) {
238e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira            mGestureDetector.onTouchEvent(event);
239e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira        }
240e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira
241847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor        final int count = event.getPointerCount();
242772fcb96f19fbd6b86ac562863714019909634bfMady Mellor        final boolean isStylusButtonDown =
243772fcb96f19fbd6b86ac562863714019909634bfMady Mellor                (event.getButtonState() & MotionEvent.BUTTON_STYLUS_PRIMARY) != 0;
244847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor
245847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor        final boolean anchoredScaleCancelled =
2466895518d48f265371c26ab4c0d300c3313c4c210Mady Mellor                mAnchoredScaleMode == ANCHORED_SCALE_MODE_STYLUS && !isStylusButtonDown;
247618cbea4e746196cbde43746706bec02e14b487bAdam Powell        final boolean streamComplete = action == MotionEvent.ACTION_UP ||
248847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor                action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;
249e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira
250618cbea4e746196cbde43746706bec02e14b487bAdam Powell        if (action == MotionEvent.ACTION_DOWN || streamComplete) {
251618cbea4e746196cbde43746706bec02e14b487bAdam Powell            // Reset any scale in progress with the listener.
252618cbea4e746196cbde43746706bec02e14b487bAdam Powell            // If it's an ACTION_DOWN we're beginning a new event stream.
253618cbea4e746196cbde43746706bec02e14b487bAdam Powell            // This means the app probably didn't give us all the events. Shame on it.
254618cbea4e746196cbde43746706bec02e14b487bAdam Powell            if (mInProgress) {
255618cbea4e746196cbde43746706bec02e14b487bAdam Powell                mListener.onScaleEnd(this);
256618cbea4e746196cbde43746706bec02e14b487bAdam Powell                mInProgress = false;
25747ec2fb37046797bebc82b505a13c552021b9ff3Adam Powell                mInitialSpan = 0;
258847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor                mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
259847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor            } else if (inAnchoredScaleMode() && streamComplete) {
2605823352c6c5bfa3824afacd023b01af537b5dfa0Mindy Pereira                mInProgress = false;
2615823352c6c5bfa3824afacd023b01af537b5dfa0Mindy Pereira                mInitialSpan = 0;
262847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor                mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
263ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            }
264618cbea4e746196cbde43746706bec02e14b487bAdam Powell
265618cbea4e746196cbde43746706bec02e14b487bAdam Powell            if (streamComplete) {
266618cbea4e746196cbde43746706bec02e14b487bAdam Powell                return true;
267ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell            }
268ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
269bbdc50b102faf52768ac3028bc49e027ff140656Jeff Brown
2706895518d48f265371c26ab4c0d300c3313c4c210Mady Mellor        if (!mInProgress && mStylusScaleEnabled && !inAnchoredScaleMode()
2716895518d48f265371c26ab4c0d300c3313c4c210Mady Mellor                && !streamComplete && isStylusButtonDown) {
272847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor            // Start of a button scale gesture
273847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor            mAnchoredScaleStartX = event.getX();
274847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor            mAnchoredScaleStartY = event.getY();
2756895518d48f265371c26ab4c0d300c3313c4c210Mady Mellor            mAnchoredScaleMode = ANCHORED_SCALE_MODE_STYLUS;
276847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor            mInitialSpan = 0;
277847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor        }
278847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor
279abde042a824c3fc2f3537ef136017dfa006258b9Adam Powell        final boolean configChanged = action == MotionEvent.ACTION_DOWN ||
280618cbea4e746196cbde43746706bec02e14b487bAdam Powell                action == MotionEvent.ACTION_POINTER_UP ||
281847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor                action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled;
282e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira
283618cbea4e746196cbde43746706bec02e14b487bAdam Powell        final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
284618cbea4e746196cbde43746706bec02e14b487bAdam Powell        final int skipIndex = pointerUp ? event.getActionIndex() : -1;
285618cbea4e746196cbde43746706bec02e14b487bAdam Powell
286618cbea4e746196cbde43746706bec02e14b487bAdam Powell        // Determine focal point
287618cbea4e746196cbde43746706bec02e14b487bAdam Powell        float sumX = 0, sumY = 0;
288618cbea4e746196cbde43746706bec02e14b487bAdam Powell        final int div = pointerUp ? count - 1 : count;
289e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira        final float focusX;
290e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira        final float focusY;
291847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor        if (inAnchoredScaleMode()) {
292847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor            // In anchored scale mode, the focal pt is always where the double tap
293847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor            // or button down gesture started
294847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor            focusX = mAnchoredScaleStartX;
295847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor            focusY = mAnchoredScaleStartY;
296e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira            if (event.getY() < focusY) {
297e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira                mEventBeforeOrAboveStartingGestureEvent = true;
298e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira            } else {
299e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira                mEventBeforeOrAboveStartingGestureEvent = false;
300e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira            }
301e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira        } else {
302e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira            for (int i = 0; i < count; i++) {
303e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira                if (skipIndex == i) continue;
304e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira                sumX += event.getX(i);
305e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira                sumY += event.getY(i);
306e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira            }
307618cbea4e746196cbde43746706bec02e14b487bAdam Powell
308e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira            focusX = sumX / div;
309e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira            focusY = sumY / div;
310e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira        }
3115b5c414e31c4a8433a3290b931687a05dadc97b6Adam Powell
312618cbea4e746196cbde43746706bec02e14b487bAdam Powell        // Determine average deviation from focal point
313618cbea4e746196cbde43746706bec02e14b487bAdam Powell        float devSumX = 0, devSumY = 0;
314618cbea4e746196cbde43746706bec02e14b487bAdam Powell        for (int i = 0; i < count; i++) {
315618cbea4e746196cbde43746706bec02e14b487bAdam Powell            if (skipIndex == i) continue;
316828e56ea6e8f4d5aa650052580f1f7873ad8296dAdam Powell
3175b5c414e31c4a8433a3290b931687a05dadc97b6Adam Powell            // Convert the resulting diameter into a radius.
318c6df18f5fa9f08183af96de7433fd80d74325318Adam Powell            devSumX += Math.abs(event.getX(i) - focusX);
319c6df18f5fa9f08183af96de7433fd80d74325318Adam Powell            devSumY += Math.abs(event.getY(i) - focusY);
320e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell        }
321618cbea4e746196cbde43746706bec02e14b487bAdam Powell        final float devX = devSumX / div;
322618cbea4e746196cbde43746706bec02e14b487bAdam Powell        final float devY = devSumY / div;
323618cbea4e746196cbde43746706bec02e14b487bAdam Powell
324618cbea4e746196cbde43746706bec02e14b487bAdam Powell        // Span is the average distance between touch points through the focal point;
325618cbea4e746196cbde43746706bec02e14b487bAdam Powell        // i.e. the diameter of the circle with a radius of the average deviation from
326618cbea4e746196cbde43746706bec02e14b487bAdam Powell        // the focal point.
327618cbea4e746196cbde43746706bec02e14b487bAdam Powell        final float spanX = devX * 2;
328618cbea4e746196cbde43746706bec02e14b487bAdam Powell        final float spanY = devY * 2;
329e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira        final float span;
330847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor        if (inAnchoredScaleMode()) {
331e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira            span = spanY;
332e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira        } else {
33333253a4baa6279f81a73425b49dfb6abe5f5416eNeil Fuller            span = (float) Math.hypot(spanX, spanY);
334e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira        }
335618cbea4e746196cbde43746706bec02e14b487bAdam Powell
336618cbea4e746196cbde43746706bec02e14b487bAdam Powell        // Dispatch begin/end events as needed.
337618cbea4e746196cbde43746706bec02e14b487bAdam Powell        // If the configuration changes, notify the app to reset its current state by beginning
338618cbea4e746196cbde43746706bec02e14b487bAdam Powell        // a fresh scale event stream.
33947ec2fb37046797bebc82b505a13c552021b9ff3Adam Powell        final boolean wasInProgress = mInProgress;
34047ec2fb37046797bebc82b505a13c552021b9ff3Adam Powell        mFocusX = focusX;
34147ec2fb37046797bebc82b505a13c552021b9ff3Adam Powell        mFocusY = focusY;
342847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor        if (!inAnchoredScaleMode() && mInProgress && (span < mMinSpan || configChanged)) {
343618cbea4e746196cbde43746706bec02e14b487bAdam Powell            mListener.onScaleEnd(this);
344618cbea4e746196cbde43746706bec02e14b487bAdam Powell            mInProgress = false;
34547ec2fb37046797bebc82b505a13c552021b9ff3Adam Powell            mInitialSpan = span;
346618cbea4e746196cbde43746706bec02e14b487bAdam Powell        }
347618cbea4e746196cbde43746706bec02e14b487bAdam Powell        if (configChanged) {
348618cbea4e746196cbde43746706bec02e14b487bAdam Powell            mPrevSpanX = mCurrSpanX = spanX;
349618cbea4e746196cbde43746706bec02e14b487bAdam Powell            mPrevSpanY = mCurrSpanY = spanY;
35047ec2fb37046797bebc82b505a13c552021b9ff3Adam Powell            mInitialSpan = mPrevSpan = mCurrSpan = span;
351618cbea4e746196cbde43746706bec02e14b487bAdam Powell        }
35224870ce4f222fec664a24e47cf6b12db36dbdff2Mindy Pereira
353847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor        final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan;
35424870ce4f222fec664a24e47cf6b12db36dbdff2Mindy Pereira        if (!mInProgress && span >=  minSpan &&
35547ec2fb37046797bebc82b505a13c552021b9ff3Adam Powell                (wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {
35647ec2fb37046797bebc82b505a13c552021b9ff3Adam Powell            mPrevSpanX = mCurrSpanX = spanX;
35747ec2fb37046797bebc82b505a13c552021b9ff3Adam Powell            mPrevSpanY = mCurrSpanY = spanY;
35847ec2fb37046797bebc82b505a13c552021b9ff3Adam Powell            mPrevSpan = mCurrSpan = span;
3597232b0ad675402222bce3b478dbb682848d5e9e6Adam Powell            mPrevTime = mCurrTime;
360618cbea4e746196cbde43746706bec02e14b487bAdam Powell            mInProgress = mListener.onScaleBegin(this);
361ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        }
362ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
363618cbea4e746196cbde43746706bec02e14b487bAdam Powell        // Handle motion; focal point and span/scale factor are changing.
364618cbea4e746196cbde43746706bec02e14b487bAdam Powell        if (action == MotionEvent.ACTION_MOVE) {
365618cbea4e746196cbde43746706bec02e14b487bAdam Powell            mCurrSpanX = spanX;
366618cbea4e746196cbde43746706bec02e14b487bAdam Powell            mCurrSpanY = spanY;
367618cbea4e746196cbde43746706bec02e14b487bAdam Powell            mCurrSpan = span;
368618cbea4e746196cbde43746706bec02e14b487bAdam Powell
369618cbea4e746196cbde43746706bec02e14b487bAdam Powell            boolean updatePrev = true;
370e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira
371618cbea4e746196cbde43746706bec02e14b487bAdam Powell            if (mInProgress) {
372618cbea4e746196cbde43746706bec02e14b487bAdam Powell                updatePrev = mListener.onScale(this);
373618cbea4e746196cbde43746706bec02e14b487bAdam Powell            }
374e33cef8037cb87386e17bcf8701a47452d262fa6Adam Powell
375618cbea4e746196cbde43746706bec02e14b487bAdam Powell            if (updatePrev) {
376618cbea4e746196cbde43746706bec02e14b487bAdam Powell                mPrevSpanX = mCurrSpanX;
377618cbea4e746196cbde43746706bec02e14b487bAdam Powell                mPrevSpanY = mCurrSpanY;
378618cbea4e746196cbde43746706bec02e14b487bAdam Powell                mPrevSpan = mCurrSpan;
3797232b0ad675402222bce3b478dbb682848d5e9e6Adam Powell                mPrevTime = mCurrTime;
380d0197f3669efda060c7ee2069ff41bd970fd6d9cAdam Powell            }
381d0197f3669efda060c7ee2069ff41bd970fd6d9cAdam Powell        }
382d0197f3669efda060c7ee2069ff41bd970fd6d9cAdam Powell
383618cbea4e746196cbde43746706bec02e14b487bAdam Powell        return true;
384ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
385ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
386847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor    private boolean inAnchoredScaleMode() {
387847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor        return mAnchoredScaleMode != ANCHORED_SCALE_MODE_NONE;
388e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira    }
389e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira
390e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira    /**
391e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     * Set whether the associated {@link OnScaleGestureListener} should receive onScale callbacks
392e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     * when the user performs a doubleTap followed by a swipe. Note that this is enabled by default
393e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     * if the app targets API 19 and newer.
394e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     * @param scales true to enable quick scaling, false to disable
395e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira     */
396e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira    public void setQuickScaleEnabled(boolean scales) {
3979f1221f87e4762457c8fa0b4c0e5a291d9aef5c9Mindy Pereira        mQuickScaleEnabled = scales;
3989f1221f87e4762457c8fa0b4c0e5a291d9aef5c9Mindy Pereira        if (mQuickScaleEnabled && mGestureDetector == null) {
399e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira            GestureDetector.SimpleOnGestureListener gestureListener =
400e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira                    new GestureDetector.SimpleOnGestureListener() {
401e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira                        @Override
402e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira                        public boolean onDoubleTap(MotionEvent e) {
403e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira                            // Double tap: start watching for a swipe
404847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor                            mAnchoredScaleStartX = e.getX();
405847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor                            mAnchoredScaleStartY = e.getY();
406847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor                            mAnchoredScaleMode = ANCHORED_SCALE_MODE_DOUBLE_TAP;
407e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira                            return true;
408e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira                        }
40924870ce4f222fec664a24e47cf6b12db36dbdff2Mindy Pereira                    };
410e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira            mGestureDetector = new GestureDetector(mContext, gestureListener, mHandler);
411e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira        }
412e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira    }
413e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira
4149f1221f87e4762457c8fa0b4c0e5a291d9aef5c9Mindy Pereira  /**
4159f1221f87e4762457c8fa0b4c0e5a291d9aef5c9Mindy Pereira   * Return whether the quick scale gesture, in which the user performs a double tap followed by a
4169f1221f87e4762457c8fa0b4c0e5a291d9aef5c9Mindy Pereira   * swipe, should perform scaling. {@see #setQuickScaleEnabled(boolean)}.
4179f1221f87e4762457c8fa0b4c0e5a291d9aef5c9Mindy Pereira   */
4189f1221f87e4762457c8fa0b4c0e5a291d9aef5c9Mindy Pereira    public boolean isQuickScaleEnabled() {
4199f1221f87e4762457c8fa0b4c0e5a291d9aef5c9Mindy Pereira        return mQuickScaleEnabled;
4209f1221f87e4762457c8fa0b4c0e5a291d9aef5c9Mindy Pereira    }
4219f1221f87e4762457c8fa0b4c0e5a291d9aef5c9Mindy Pereira
422ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
4236895518d48f265371c26ab4c0d300c3313c4c210Mady Mellor     * Sets whether the associates {@link OnScaleGestureListener} should receive
4246895518d48f265371c26ab4c0d300c3313c4c210Mady Mellor     * onScale callbacks when the user uses a stylus and presses the button.
4256895518d48f265371c26ab4c0d300c3313c4c210Mady Mellor     * Note that this is enabled by default if the app targets API 23 and newer.
426847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor     *
4276895518d48f265371c26ab4c0d300c3313c4c210Mady Mellor     * @param scales true to enable stylus scaling, false to disable.
428847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor     */
4296895518d48f265371c26ab4c0d300c3313c4c210Mady Mellor    public void setStylusScaleEnabled(boolean scales) {
4306895518d48f265371c26ab4c0d300c3313c4c210Mady Mellor        mStylusScaleEnabled = scales;
431847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor    }
432847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor
433847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor    /**
434d9ff4df850e9e22915d09047c953765c84dcfb84Mady Mellor     * Return whether the stylus scale gesture, in which the user uses a stylus and presses the
435d9ff4df850e9e22915d09047c953765c84dcfb84Mady Mellor     * button, should perform scaling. {@see #setStylusScaleEnabled(boolean)}
436847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor     */
4376895518d48f265371c26ab4c0d300c3313c4c210Mady Mellor    public boolean isStylusScaleEnabled() {
4386895518d48f265371c26ab4c0d300c3313c4c210Mady Mellor        return mStylusScaleEnabled;
439847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor    }
440847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor
441847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor    /**
442618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * Returns {@code true} if a scale gesture is in progress.
443ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
444ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public boolean isInProgress() {
445618cbea4e746196cbde43746706bec02e14b487bAdam Powell        return mInProgress;
446ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
447ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
448ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
449ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Get the X coordinate of the current gesture's focal point.
450618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * If a gesture is in progress, the focal point is between
451618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * each of the pointers forming the gesture.
452618cbea4e746196cbde43746706bec02e14b487bAdam Powell     *
453ab905c87b7324d15715b78eaae7ef8558ad3bd10Adam Powell     * If {@link #isInProgress()} would return false, the result of this
454ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * function is undefined.
45547c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
456ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return X coordinate of the focal point in pixels.
457ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
458ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public float getFocusX() {
459ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        return mFocusX;
460ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
461ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
462ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
463ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Get the Y coordinate of the current gesture's focal point.
464618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * If a gesture is in progress, the focal point is between
465618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * each of the pointers forming the gesture.
466618cbea4e746196cbde43746706bec02e14b487bAdam Powell     *
467ab905c87b7324d15715b78eaae7ef8558ad3bd10Adam Powell     * If {@link #isInProgress()} would return false, the result of this
468ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * function is undefined.
46947c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
470ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return Y coordinate of the focal point in pixels.
471ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
472ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public float getFocusY() {
473ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell        return mFocusY;
474ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
475ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
476ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
477618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * Return the average distance between each of the pointers forming the
478618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * gesture in progress through the focal point.
47947c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
480ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return Distance between pointers in pixels.
481ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
482ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public float getCurrentSpan() {
483618cbea4e746196cbde43746706bec02e14b487bAdam Powell        return mCurrSpan;
484ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
485ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
486ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
487618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * Return the average X distance between each of the pointers forming the
488618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * gesture in progress through the focal point.
48947c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
49047c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * @return Distance between pointers in pixels.
49147c41e807e36999e4d0d2072e41a82bc45655ff2Erik     */
49247c41e807e36999e4d0d2072e41a82bc45655ff2Erik    public float getCurrentSpanX() {
493618cbea4e746196cbde43746706bec02e14b487bAdam Powell        return mCurrSpanX;
49447c41e807e36999e4d0d2072e41a82bc45655ff2Erik    }
49547c41e807e36999e4d0d2072e41a82bc45655ff2Erik
49647c41e807e36999e4d0d2072e41a82bc45655ff2Erik    /**
497618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * Return the average Y distance between each of the pointers forming the
498618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * gesture in progress through the focal point.
49947c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
50047c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * @return Distance between pointers in pixels.
50147c41e807e36999e4d0d2072e41a82bc45655ff2Erik     */
50247c41e807e36999e4d0d2072e41a82bc45655ff2Erik    public float getCurrentSpanY() {
503618cbea4e746196cbde43746706bec02e14b487bAdam Powell        return mCurrSpanY;
50447c41e807e36999e4d0d2072e41a82bc45655ff2Erik    }
50547c41e807e36999e4d0d2072e41a82bc45655ff2Erik
50647c41e807e36999e4d0d2072e41a82bc45655ff2Erik    /**
507618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * Return the previous average distance between each of the pointers forming the
508618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * gesture in progress through the focal point.
50947c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
510ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return Previous distance between pointers in pixels.
511ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
512ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public float getPreviousSpan() {
513618cbea4e746196cbde43746706bec02e14b487bAdam Powell        return mPrevSpan;
514ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
515ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell
516ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
517618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * Return the previous average X distance between each of the pointers forming the
518618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * gesture in progress through the focal point.
51947c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
52047c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * @return Previous distance between pointers in pixels.
52147c41e807e36999e4d0d2072e41a82bc45655ff2Erik     */
52247c41e807e36999e4d0d2072e41a82bc45655ff2Erik    public float getPreviousSpanX() {
523618cbea4e746196cbde43746706bec02e14b487bAdam Powell        return mPrevSpanX;
52447c41e807e36999e4d0d2072e41a82bc45655ff2Erik    }
52547c41e807e36999e4d0d2072e41a82bc45655ff2Erik
52647c41e807e36999e4d0d2072e41a82bc45655ff2Erik    /**
527618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * Return the previous average Y distance between each of the pointers forming the
528618cbea4e746196cbde43746706bec02e14b487bAdam Powell     * gesture in progress through the focal point.
52947c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
53047c41e807e36999e4d0d2072e41a82bc45655ff2Erik     * @return Previous distance between pointers in pixels.
53147c41e807e36999e4d0d2072e41a82bc45655ff2Erik     */
53247c41e807e36999e4d0d2072e41a82bc45655ff2Erik    public float getPreviousSpanY() {
533618cbea4e746196cbde43746706bec02e14b487bAdam Powell        return mPrevSpanY;
53447c41e807e36999e4d0d2072e41a82bc45655ff2Erik    }
53547c41e807e36999e4d0d2072e41a82bc45655ff2Erik
53647c41e807e36999e4d0d2072e41a82bc45655ff2Erik    /**
537ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Return the scaling factor from the previous scale event to the current
538ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * event. This value is defined as
539ab905c87b7324d15715b78eaae7ef8558ad3bd10Adam Powell     * ({@link #getCurrentSpan()} / {@link #getPreviousSpan()}).
54047c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
541ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return The current scaling factor.
542ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
543ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public float getScaleFactor() {
544847d17fcba7e0fab3093d05b5405554df91c08e2Mady Mellor        if (inAnchoredScaleMode()) {
545e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira            // Drag is moving up; the further away from the gesture
546e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira            // start, the smaller the span should be, the closer,
547e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira            // the larger the span, and therefore the larger the scale
54824870ce4f222fec664a24e47cf6b12db36dbdff2Mindy Pereira            final boolean scaleUp =
54924870ce4f222fec664a24e47cf6b12db36dbdff2Mindy Pereira                    (mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan < mPrevSpan)) ||
55024870ce4f222fec664a24e47cf6b12db36dbdff2Mindy Pereira                    (!mEventBeforeOrAboveStartingGestureEvent && (mCurrSpan > mPrevSpan));
55124870ce4f222fec664a24e47cf6b12db36dbdff2Mindy Pereira            final float spanDiff = (Math.abs(1 - (mCurrSpan / mPrevSpan)) * SCALE_FACTOR);
55224870ce4f222fec664a24e47cf6b12db36dbdff2Mindy Pereira            return mPrevSpan <= 0 ? 1 : scaleUp ? (1 + spanDiff) : (1 - spanDiff);
553e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira        }
554618cbea4e746196cbde43746706bec02e14b487bAdam Powell        return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
555ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
55647c41e807e36999e4d0d2072e41a82bc45655ff2Erik
557ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
558ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Return the time difference in milliseconds between the previous
559ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * accepted scaling event and the current scaling event.
56047c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
561ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return Time difference since the last scaling event in milliseconds.
562ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
563ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public long getTimeDelta() {
564618cbea4e746196cbde43746706bec02e14b487bAdam Powell        return mCurrTime - mPrevTime;
565ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
56647c41e807e36999e4d0d2072e41a82bc45655ff2Erik
567ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    /**
568ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * Return the event time of the current event being processed.
56947c41e807e36999e4d0d2072e41a82bc45655ff2Erik     *
570ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     * @return Current event time in milliseconds.
571ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell     */
572ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    public long getEventTime() {
573618cbea4e746196cbde43746706bec02e14b487bAdam Powell        return mCurrTime;
574ae542ff055301a4c3c8a18e8da1739df3a771958Adam Powell    }
575e8ce8ba2b5633e479ccaa82d8e3147ccbba62961Mindy Pereira}