blob: d97b022381efc5e297a5c8bca3596dab4484afab [file] [log] [blame]
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001/*
2 ** Copyright 2011, The Android Open Source Project
3 **
4 ** Licensed under the Apache License, Version 2.0 (the "License");
5 ** you may not use this file except in compliance with the License.
6 ** You may obtain a copy of the License at
7 **
8 ** http://www.apache.org/licenses/LICENSE-2.0
9 **
10 ** Unless required by applicable law or agreed to in writing, software
11 ** distributed under the License is distributed on an "AS IS" BASIS,
12 ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 ** See the License for the specific language governing permissions and
14 ** limitations under the License.
15 */
16
17package com.android.server.accessibility;
18
Svetoslav Ganov736c2752011-04-22 18:30:36 -070019import android.content.Context;
Svetoslav Ganov42138042012-03-20 11:51:39 -070020import android.gesture.Gesture;
21import android.gesture.GestureLibraries;
22import android.gesture.GestureLibrary;
23import android.gesture.GesturePoint;
24import android.gesture.GestureStroke;
25import android.gesture.Prediction;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -070026import android.graphics.Rect;
Svetoslav Ganov736c2752011-04-22 18:30:36 -070027import android.os.Handler;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -070028import android.os.SystemClock;
Svetoslav Ganov736c2752011-04-22 18:30:36 -070029import android.util.Slog;
Svetoslav Ganov736c2752011-04-22 18:30:36 -070030import android.view.MotionEvent;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -070031import android.view.MotionEvent.PointerCoords;
32import android.view.MotionEvent.PointerProperties;
Svetoslav Ganov42138042012-03-20 11:51:39 -070033import android.view.VelocityTracker;
Svetoslav Ganovf5a07902011-07-24 19:20:17 -070034import android.view.ViewConfiguration;
35import android.view.WindowManagerPolicy;
Svetoslav Ganov736c2752011-04-22 18:30:36 -070036
Svetoslav Ganov42138042012-03-20 11:51:39 -070037import com.android.internal.R;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -070038import com.android.server.input.InputFilter;
Svetoslav Ganovf5a07902011-07-24 19:20:17 -070039
Svetoslav Ganov42138042012-03-20 11:51:39 -070040import java.util.ArrayList;
Svetoslav Ganov736c2752011-04-22 18:30:36 -070041import java.util.Arrays;
42
43/**
44 * This class is a strategy for performing touch exploration. It
45 * transforms the motion event stream by modifying, adding, replacing,
46 * and consuming certain events. The interaction model is:
47 *
48 * <ol>
Svetoslav Ganove15ccb92012-05-16 15:48:55 -070049 * <li>1. One finger moving slow around performs touch exploration.</li>
50 * <li>2. One finger moving fast around performs gestures.</li>
51 * <li>3. Two close fingers moving in the same direction perform a drag.</li>
52 * <li>4. Multi-finger gestures are delivered to view hierarchy.</li>
53 * <li>5. Pointers that have not moved more than a specified distance after they
Svetoslav Ganov736c2752011-04-22 18:30:36 -070054 * went down are considered inactive.</li>
Svetoslav Ganove15ccb92012-05-16 15:48:55 -070055 * <li>6. Two fingers moving in different directions are considered a multi-finger gesture.</li>
56 * <li>7. Double tapping clicks on the on the last touch explored location of it was in
57 * a window that does not take focus, otherwise the click is within the accessibility
58 * focused rectangle.</li>
59 * <li>7. Tapping and holding for a while performs a long press in a similar fashion
60 * as the click above.</li>
Svetoslav Ganov736c2752011-04-22 18:30:36 -070061 * <ol>
62 *
63 * @hide
64 */
Svetoslav Ganov42138042012-03-20 11:51:39 -070065public class TouchExplorer {
66
Svetoslav Ganov736c2752011-04-22 18:30:36 -070067 private static final boolean DEBUG = false;
68
69 // Tag for logging received events.
Svetoslav Ganov42138042012-03-20 11:51:39 -070070 private static final String LOG_TAG = "TouchExplorer";
Svetoslav Ganov736c2752011-04-22 18:30:36 -070071
72 // States this explorer can be in.
73 private static final int STATE_TOUCH_EXPLORING = 0x00000001;
74 private static final int STATE_DRAGGING = 0x00000002;
75 private static final int STATE_DELEGATING = 0x00000004;
Svetoslav Ganov42138042012-03-20 11:51:39 -070076 private static final int STATE_GESTURE_DETECTING = 0x00000005;
Svetoslav Ganov736c2752011-04-22 18:30:36 -070077
Svetoslav Ganov736c2752011-04-22 18:30:36 -070078 // The minimum of the cosine between the vectors of two moving
79 // pointers so they can be considered moving in the same direction.
Svetoslav Ganov12a024c2011-09-03 19:52:36 -070080 private static final float MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4)
Svetoslav Ganov736c2752011-04-22 18:30:36 -070081
Svetoslav Ganovf8044202011-08-26 20:33:33 -070082 // Constant referring to the ids bits of all pointers.
83 private static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF;
Svetoslav Ganov736c2752011-04-22 18:30:36 -070084
Svetoslav Ganov42138042012-03-20 11:51:39 -070085 // This constant captures the current implementation detail that
86 // pointer IDs are between 0 and 31 inclusive (subject to change).
87 // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
Svetoslav Ganove15ccb92012-05-16 15:48:55 -070088 private static final int MAX_POINTER_COUNT = 32;
Svetoslav Ganov42138042012-03-20 11:51:39 -070089
90 // Invalid pointer ID.
Svetoslav Ganove15ccb92012-05-16 15:48:55 -070091 private static final int INVALID_POINTER_ID = -1;
92
93 // The velocity above which we detect gestures.
94 private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000;
95
96 // The minimal distance before we take the middle of the distance between
97 // the two dragging pointers as opposed to use the location of the primary one.
98 private static final int MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP = 200;
Svetoslav Ganov42138042012-03-20 11:51:39 -070099
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700100 // Temporary array for storing pointer IDs.
101 private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT];
102
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700103 // Timeout within which we try to detect a tap.
104 private final int mTapTimeout;
105
106 // Timeout within which we try to detect a double tap.
107 private final int mDoubleTapTimeout;
108
109 // Slop between the down and up tap to be a tap.
110 private final int mTouchSlop;
111
112 // Slop between the first and second tap to be a double tap.
113 private final int mDoubleTapSlop;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700114
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700115 // The InputFilter this tracker is associated with i.e. the filter
116 // which delegates event processing to this touch explorer.
117 private final InputFilter mInputFilter;
118
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700119 // The current state of the touch explorer.
120 private int mCurrentState = STATE_TOUCH_EXPLORING;
121
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700122 // The ID of the pointer used for dragging.
123 private int mDraggingPointerId;
124
125 // Handler for performing asynchronous operations.
126 private final Handler mHandler;
127
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700128 // Command for delayed sending of a hover enter event.
129 private final SendHoverDelayed mSendHoverEnterDelayed;
130
131 // Command for delayed sending of a hover exit event.
132 private final SendHoverDelayed mSendHoverExitDelayed;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700133
Svetoslav Ganovf5a07902011-07-24 19:20:17 -0700134 // Command for delayed sending of a long press.
135 private final PerformLongPressDelayed mPerformLongPressDelayed;
136
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700137 // Helper to detect and react to double tap in touch explore mode.
138 private final DoubleTapDetector mDoubleTapDetector;
139
140 // The scaled minimal distance before we take the middle of the distance between
141 // the two dragging pointers as opposed to use the location of the primary one.
142 private final int mScaledMinPointerDistanceToUseMiddleLocation;
143
144 // The scaled velocity above which we detect gestures.
145 private final int mScaledGestureDetectionVelocity;
146
147 // Helper to track gesture velocity.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700148 private VelocityTracker mVelocityTracker;
149
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700150 // Helper class to track received pointers.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700151 private final ReceivedPointerTracker mReceivedPointerTracker;
152
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700153 // Helper class to track injected pointers.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700154 private final InjectedPointerTracker mInjectedPointerTracker;
155
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700156 // Handle to the accessibility manager service.
157 private final AccessibilityManagerService mAms;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700158
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700159 // Temporary rectangle to avoid instantiation.
160 private final Rect mTempRect = new Rect();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700161
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700162 // The X of the previous event.
163 private float mPreviousX;
164
165 // The Y of the previous event.
166 private float mPreviousY;
167
168 // Buffer for storing points for gesture detection.
169 private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
170
171 // The minimal delta between moves to add a gesture point.
172 private static final int TOUCH_TOLERANCE = 3;
173
174 // The minimal score for accepting a predicted gesture.
175 private static final float MIN_PREDICTION_SCORE = 2.0f;
176
177 // The library for gesture detection.
178 private GestureLibrary mGestureLibrary;
179
180 // The long pressing pointer id if coordinate remapping is needed.
181 private int mLongPressingPointerId;
182
183 // The long pressing pointer X if coordinate remapping is needed.
184 private int mLongPressingPointerDeltaX;
185
186 // The long pressing pointer Y if coordinate remapping is needed.
187 private int mLongPressingPointerDeltaY;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700188
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700189 /**
190 * Creates a new instance.
191 *
192 * @param inputFilter The input filter associated with this explorer.
193 * @param context A context handle for accessing resources.
194 */
Svetoslav Ganov42138042012-03-20 11:51:39 -0700195 public TouchExplorer(InputFilter inputFilter, Context context,
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700196 AccessibilityManagerService service) {
197 mAms = service;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700198 mReceivedPointerTracker = new ReceivedPointerTracker(context);
199 mInjectedPointerTracker = new InjectedPointerTracker();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700200 mInputFilter = inputFilter;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700201 mTapTimeout = ViewConfiguration.getTapTimeout();
202 mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
203 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
204 mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700205 mHandler = new Handler(context.getMainLooper());
Svetoslav Ganovf5a07902011-07-24 19:20:17 -0700206 mPerformLongPressDelayed = new PerformLongPressDelayed();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700207 mGestureLibrary = GestureLibraries.fromRawResource(context, R.raw.accessibility_gestures);
208 mGestureLibrary.setOrientationStyle(4);
209 mGestureLibrary.load();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700210 mSendHoverEnterDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_ENTER, true);
211 mSendHoverExitDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_EXIT, false);
212 mDoubleTapDetector = new DoubleTapDetector();
213 final float density = context.getResources().getDisplayMetrics().density;
214 mScaledMinPointerDistanceToUseMiddleLocation =
215 (int) (MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP * density);
216 mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density);
217 }
218
219 public void clear() {
220 // If we have not received an event then we are in initial
221 // state. Therefore, there is not need to clean anything.
222 MotionEvent event = mReceivedPointerTracker.getLastReceivedEvent();
223 if (event != null) {
224 clear(mReceivedPointerTracker.getLastReceivedEvent(), WindowManagerPolicy.FLAG_TRUSTED);
225 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700226 }
227
228 public void clear(MotionEvent event, int policyFlags) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700229 switch (mCurrentState) {
230 case STATE_TOUCH_EXPLORING: {
231 // If a touch exploration gesture is in progress send events for its end.
232 sendExitEventsIfNeeded(policyFlags);
233 } break;
234 case STATE_DRAGGING: {
235 mDraggingPointerId = INVALID_POINTER_ID;
236 // Send exit to all pointers that we have delivered.
237 sendUpForInjectedDownPointers(event, policyFlags);
238 } break;
239 case STATE_DELEGATING: {
240 // Send exit to all pointers that we have delivered.
241 sendUpForInjectedDownPointers(event, policyFlags);
242 } break;
243 case STATE_GESTURE_DETECTING: {
244 // Clear the current stroke.
245 mStrokeBuffer.clear();
246 } break;
247 }
248 // Remove all pending callbacks.
249 mSendHoverEnterDelayed.remove();
250 mSendHoverExitDelayed.remove();
251 mPerformLongPressDelayed.remove();
252 // Reset the pointer trackers.
253 mReceivedPointerTracker.clear();
254 mInjectedPointerTracker.clear();
255 // Clear the double tap detector
256 mDoubleTapDetector.clear();
257 // Go to initial state.
258 // Clear the long pressing pointer remap data.
259 mLongPressingPointerId = -1;
260 mLongPressingPointerDeltaX = 0;
261 mLongPressingPointerDeltaY = 0;
262 mCurrentState = STATE_TOUCH_EXPLORING;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700263 }
264
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700265 public void onMotionEvent(MotionEvent event, int policyFlags) {
266 if (DEBUG) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700267 Slog.d(LOG_TAG, "Received event: " + event + ", policyFlags=0x"
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700268 + Integer.toHexString(policyFlags));
Svetoslav Ganov42138042012-03-20 11:51:39 -0700269 Slog.d(LOG_TAG, getStateSymbolicName(mCurrentState));
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700270 }
271
Svetoslav Ganov42138042012-03-20 11:51:39 -0700272 mReceivedPointerTracker.onMotionEvent(event);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700273
274 switch(mCurrentState) {
275 case STATE_TOUCH_EXPLORING: {
276 handleMotionEventStateTouchExploring(event, policyFlags);
277 } break;
278 case STATE_DRAGGING: {
279 handleMotionEventStateDragging(event, policyFlags);
280 } break;
281 case STATE_DELEGATING: {
282 handleMotionEventStateDelegating(event, policyFlags);
283 } break;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700284 case STATE_GESTURE_DETECTING: {
285 handleMotionEventGestureDetecting(event, policyFlags);
286 } break;
287 default:
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700288 throw new IllegalStateException("Illegal state: " + mCurrentState);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700289 }
290 }
291
292 /**
293 * Handles a motion event in touch exploring state.
294 *
295 * @param event The event to be handled.
296 * @param policyFlags The policy flags associated with the event.
297 */
298 private void handleMotionEventStateTouchExploring(MotionEvent event, int policyFlags) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700299 ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700300 final int activePointerCount = receivedTracker.getActivePointerCount();
301
302 if (mVelocityTracker == null) {
303 mVelocityTracker = VelocityTracker.obtain();
304 }
305 mVelocityTracker.addMovement(event);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700306
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700307 mDoubleTapDetector.onMotionEvent(event, policyFlags);
308
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700309 switch (event.getActionMasked()) {
Svetoslav Ganovf5a07902011-07-24 19:20:17 -0700310 case MotionEvent.ACTION_DOWN:
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700311 // Pre-feed the motion events to the gesture detector since we
312 // have a distance slop before getting into gesture detection
313 // mode and not using the points within this slop significantly
314 // decreases the quality of gesture recognition.
315 handleMotionEventGestureDetecting(event, policyFlags);
316 //$FALL-THROUGH$
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700317 case MotionEvent.ACTION_POINTER_DOWN: {
318 switch (activePointerCount) {
319 case 0: {
320 throw new IllegalStateException("The must always be one active pointer in"
321 + "touch exploring state!");
322 }
323 case 1: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700324 // If we still have not notified the user for the last
325 // touch, we figure out what to do. If were waiting
326 // we resent the delayed callback and wait again.
327 if (mSendHoverEnterDelayed.isPending()) {
328 mSendHoverEnterDelayed.remove();
329 mSendHoverExitDelayed.remove();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700330 }
331
Svetoslav Ganovebac1b72012-06-02 16:26:44 -0700332 mPerformLongPressDelayed.remove();
333
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700334 // If we have the first tap schedule a long press and break
335 // since we do not want to schedule hover enter because
336 // the delayed callback will kick in before the long click.
337 // This would lead to a state transition resulting in long
338 // pressing the item below the double taped area which is
339 // not necessary where accessibility focus is.
340 if (mDoubleTapDetector.firstTapDetected()) {
341 // We got a tap now post a long press action.
342 mPerformLongPressDelayed.post(event, policyFlags);
343 break;
344 }
345 // Deliver hover enter with a delay to have a chance
346 // to detect what the user is trying to do.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700347 final int pointerId = receivedTracker.getPrimaryActivePointerId();
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700348 final int pointerIdBits = (1 << pointerId);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700349 mSendHoverEnterDelayed.post(event, pointerIdBits, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700350 } break;
351 default: {
352 /* do nothing - let the code for ACTION_MOVE decide what to do */
353 } break;
354 }
355 } break;
356 case MotionEvent.ACTION_MOVE: {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700357 final int pointerId = receivedTracker.getPrimaryActivePointerId();
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700358 final int pointerIndex = event.findPointerIndex(pointerId);
359 final int pointerIdBits = (1 << pointerId);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700360 switch (activePointerCount) {
361 case 0: {
362 /* do nothing - no active pointers so we swallow the event */
363 } break;
364 case 1: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700365 // We have not started sending events since we try to
366 // figure out what the user is doing.
367 if (mSendHoverEnterDelayed.isPending()) {
368 // Pre-feed the motion events to the gesture detector since we
369 // have a distance slop before getting into gesture detection
370 // mode and not using the points within this slop significantly
371 // decreases the quality of gesture recognition.
372 handleMotionEventGestureDetecting(event, policyFlags);
373
Svetoslav Ganov42138042012-03-20 11:51:39 -0700374 final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700375 - event.getX(pointerIndex);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700376 final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700377 - event.getY(pointerIndex);
378 final double moveDelta = Math.hypot(deltaX, deltaY);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700379 // The user has moved enough for us to decide.
380 if (moveDelta > mDoubleTapSlop) {
381 // Check whether the user is performing a gesture. We
382 // detect gestures if the pointer is moving above a
383 // given velocity.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700384 mVelocityTracker.computeCurrentVelocity(1000);
385 final float maxAbsVelocity = Math.max(
386 Math.abs(mVelocityTracker.getXVelocity(pointerId)),
387 Math.abs(mVelocityTracker.getYVelocity(pointerId)));
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700388 if (maxAbsVelocity > mScaledGestureDetectionVelocity) {
389 // We have to perform gesture detection, so
390 // clear the current state and try to detect.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700391 mCurrentState = STATE_GESTURE_DETECTING;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700392 mSendHoverEnterDelayed.remove();
393 mSendHoverExitDelayed.remove();
394 mPerformLongPressDelayed.remove();
395 } else {
396 // We have just decided that the user is touch,
397 // exploring so start sending events.
398 mSendHoverEnterDelayed.forceSendAndRemove();
399 mSendHoverExitDelayed.remove();
Svetoslav Ganovebac1b72012-06-02 16:26:44 -0700400 mPerformLongPressDelayed.remove();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700401 sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE,
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700402 pointerIdBits, policyFlags);
403 }
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700404 break;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700405 }
406 } else {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700407 // The user is wither double tapping or performing long
408 // press so do not send move events yet.
409 if (mDoubleTapDetector.firstTapDetected()) {
410 break;
411 }
412 sendEnterEventsIfNeeded(policyFlags);
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700413 sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700414 policyFlags);
415 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700416 } break;
417 case 2: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700418 // More than one pointer so the user is not touch exploring
419 // and now we have to decide whether to delegate or drag.
420 if (mSendHoverEnterDelayed.isPending()) {
421 // We have not started sending events so cancel
422 // scheduled sending events.
423 mSendHoverEnterDelayed.remove();
424 mSendHoverExitDelayed.remove();
425 mPerformLongPressDelayed.remove();
426 } else {
Svetoslav Ganovebac1b72012-06-02 16:26:44 -0700427 mPerformLongPressDelayed.remove();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700428 // If the user is touch exploring the second pointer may be
429 // performing a double tap to activate an item without need
430 // for the user to lift his exploring finger.
431 final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
432 - event.getX(pointerIndex);
433 final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
434 - event.getY(pointerIndex);
435 final double moveDelta = Math.hypot(deltaX, deltaY);
436 if (moveDelta < mDoubleTapSlop) {
437 break;
438 }
439 // We are sending events so send exit and gesture
440 // end since we transition to another state.
441 sendExitEventsIfNeeded(policyFlags);
442 }
443
444 // We know that a new state transition is to happen and the
445 // new state will not be gesture recognition, so clear the
446 // stashed gesture strokes.
447 mStrokeBuffer.clear();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700448
449 if (isDraggingGesture(event)) {
450 // Two pointers moving in the same direction within
451 // a given distance perform a drag.
Svetoslav Ganov00f7b3f2011-06-07 19:24:10 -0700452 mCurrentState = STATE_DRAGGING;
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700453 mDraggingPointerId = pointerId;
454 sendMotionEvent(event, MotionEvent.ACTION_DOWN, pointerIdBits,
455 policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700456 } else {
457 // Two pointers moving arbitrary are delegated to the view hierarchy.
458 mCurrentState = STATE_DELEGATING;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700459 sendDownForAllActiveNotInjectedPointers(event, policyFlags);
460 }
461 } break;
462 default: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700463 // More than one pointer so the user is not touch exploring
464 // and now we have to decide whether to delegate or drag.
465 if (mSendHoverEnterDelayed.isPending()) {
466 // We have not started sending events so cancel
467 // scheduled sending events.
468 mSendHoverEnterDelayed.remove();
469 mSendHoverExitDelayed.remove();
470 mPerformLongPressDelayed.remove();
471 } else {
Svetoslav Ganovebac1b72012-06-02 16:26:44 -0700472 mPerformLongPressDelayed.remove();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700473 // We are sending events so send exit and gesture
474 // end since we transition to another state.
475 sendExitEventsIfNeeded(policyFlags);
476 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700477
478 // More than two pointers are delegated to the view hierarchy.
479 mCurrentState = STATE_DELEGATING;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700480 sendDownForAllActiveNotInjectedPointers(event, policyFlags);
481 }
482 }
483 } break;
484 case MotionEvent.ACTION_UP:
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700485 // We know that we do not need the pre-fed gesture points are not
486 // needed anymore since the last pointer just went up.
487 mStrokeBuffer.clear();
488 //$FALL-THROUGH$
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700489 case MotionEvent.ACTION_POINTER_UP: {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700490 final int pointerId = receivedTracker.getLastReceivedUpPointerId();
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700491 final int pointerIdBits = (1 << pointerId);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700492 switch (activePointerCount) {
493 case 0: {
494 // If the pointer that went up was not active we have nothing to do.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700495 if (!receivedTracker.wasLastReceivedUpPointerActive()) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700496 break;
497 }
498
Svetoslav Ganovf5a07902011-07-24 19:20:17 -0700499 mPerformLongPressDelayed.remove();
500
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700501 // If we have not delivered the enter schedule exit.
502 if (mSendHoverEnterDelayed.isPending()) {
503 mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700504 } else {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700505 // The user is touch exploring so we send events for end.
506 sendExitEventsIfNeeded(policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700507 }
508 } break;
509 }
Svetoslav Ganov42138042012-03-20 11:51:39 -0700510 if (mVelocityTracker != null) {
511 mVelocityTracker.clear();
512 mVelocityTracker = null;
513 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700514 } break;
515 case MotionEvent.ACTION_CANCEL: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700516 clear(event, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700517 } break;
518 }
519 }
520
521 /**
522 * Handles a motion event in dragging state.
523 *
524 * @param event The event to be handled.
525 * @param policyFlags The policy flags associated with the event.
526 */
527 private void handleMotionEventStateDragging(MotionEvent event, int policyFlags) {
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700528 final int pointerIdBits = (1 << mDraggingPointerId);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700529 switch (event.getActionMasked()) {
530 case MotionEvent.ACTION_DOWN: {
531 throw new IllegalStateException("Dragging state can be reached only if two "
532 + "pointers are already down");
533 }
534 case MotionEvent.ACTION_POINTER_DOWN: {
535 // We are in dragging state so we have two pointers and another one
536 // goes down => delegate the three pointers to the view hierarchy
537 mCurrentState = STATE_DELEGATING;
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700538 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700539 sendDownForAllActiveNotInjectedPointers(event, policyFlags);
540 } break;
541 case MotionEvent.ACTION_MOVE: {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700542 final int activePointerCount = mReceivedPointerTracker.getActivePointerCount();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700543 switch (activePointerCount) {
Svetoslav Ganov2e1c66b2011-10-11 18:19:10 -0700544 case 1: {
545 // do nothing
546 } break;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700547 case 2: {
548 if (isDraggingGesture(event)) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700549 // If the dragging pointer are closer that a given distance we
550 // use the location of the primary one. Otherwise, we take the
551 // middle between the pointers.
552 int[] pointerIds = mTempPointerIds;
553 mReceivedPointerTracker.populateActivePointerIds(pointerIds);
554
555 final int firstPtrIndex = event.findPointerIndex(pointerIds[0]);
556 final int secondPtrIndex = event.findPointerIndex(pointerIds[1]);
557
558 final float firstPtrX = event.getX(firstPtrIndex);
559 final float firstPtrY = event.getY(firstPtrIndex);
560 final float secondPtrX = event.getX(secondPtrIndex);
561 final float secondPtrY = event.getY(secondPtrIndex);
562
563 final float deltaX = firstPtrX - secondPtrX;
564 final float deltaY = firstPtrY - secondPtrY;
565 final double distance = Math.hypot(deltaX, deltaY);
566
567 if (distance > mScaledMinPointerDistanceToUseMiddleLocation) {
568 event.setLocation(deltaX / 2, deltaY / 2);
569 }
570
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700571 // If still dragging send a drag event.
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700572 sendMotionEvent(event, MotionEvent.ACTION_MOVE, pointerIdBits,
573 policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700574 } else {
575 // The two pointers are moving either in different directions or
576 // no close enough => delegate the gesture to the view hierarchy.
577 mCurrentState = STATE_DELEGATING;
578 // Send an event to the end of the drag gesture.
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700579 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
580 policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700581 // Deliver all active pointers to the view hierarchy.
582 sendDownForAllActiveNotInjectedPointers(event, policyFlags);
583 }
584 } break;
585 default: {
586 mCurrentState = STATE_DELEGATING;
587 // Send an event to the end of the drag gesture.
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700588 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
589 policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700590 // Deliver all active pointers to the view hierarchy.
591 sendDownForAllActiveNotInjectedPointers(event, policyFlags);
592 }
593 }
594 } break;
595 case MotionEvent.ACTION_POINTER_UP: {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700596 final int activePointerCount = mReceivedPointerTracker.getActivePointerCount();
Svetoslav Ganovd8581c72011-10-18 11:11:33 -0700597 switch (activePointerCount) {
598 case 1: {
599 // Send an event to the end of the drag gesture.
600 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
601 } break;
602 default: {
603 mCurrentState = STATE_TOUCH_EXPLORING;
604 }
605 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700606 } break;
Svetoslav Ganov2e1c66b2011-10-11 18:19:10 -0700607 case MotionEvent.ACTION_UP: {
608 mCurrentState = STATE_TOUCH_EXPLORING;
609 } break;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700610 case MotionEvent.ACTION_CANCEL: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700611 clear(event, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700612 } break;
613 }
614 }
615
616 /**
617 * Handles a motion event in delegating state.
618 *
619 * @param event The event to be handled.
620 * @param policyFlags The policy flags associated with the event.
621 */
Svetoslav Ganov2e1c66b2011-10-11 18:19:10 -0700622 private void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700623 switch (event.getActionMasked()) {
624 case MotionEvent.ACTION_DOWN: {
625 throw new IllegalStateException("Delegating state can only be reached if "
626 + "there is at least one pointer down!");
627 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700628 case MotionEvent.ACTION_MOVE: {
629 // Check whether some other pointer became active because they have moved
630 // a given distance and if such exist send them to the view hierarchy
Svetoslav Ganov42138042012-03-20 11:51:39 -0700631 final int notInjectedCount = getNotInjectedActivePointerCount(
632 mReceivedPointerTracker, mInjectedPointerTracker);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700633 if (notInjectedCount > 0) {
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700634 MotionEvent prototype = MotionEvent.obtain(event);
635 sendDownForAllActiveNotInjectedPointers(prototype, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700636 }
637 } break;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700638 case MotionEvent.ACTION_UP:
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700639 case MotionEvent.ACTION_POINTER_UP: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700640 mLongPressingPointerId = -1;
641 mLongPressingPointerDeltaX = 0;
642 mLongPressingPointerDeltaY = 0;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700643 // No active pointers => go to initial state.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700644 if (mReceivedPointerTracker.getActivePointerCount() == 0) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700645 mCurrentState = STATE_TOUCH_EXPLORING;
646 }
647 } break;
648 case MotionEvent.ACTION_CANCEL: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700649 clear(event, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700650 } break;
651 }
652 // Deliver the event striping out inactive pointers.
653 sendMotionEventStripInactivePointers(event, policyFlags);
654 }
655
Svetoslav Ganov42138042012-03-20 11:51:39 -0700656 private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) {
657 switch (event.getActionMasked()) {
658 case MotionEvent.ACTION_DOWN: {
659 final float x = event.getX();
660 final float y = event.getY();
661 mPreviousX = x;
662 mPreviousY = y;
663 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
664 } break;
665 case MotionEvent.ACTION_MOVE: {
666 final float x = event.getX();
667 final float y = event.getY();
668 final float dX = Math.abs(x - mPreviousX);
669 final float dY = Math.abs(y - mPreviousY);
670 if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) {
671 mPreviousX = x;
672 mPreviousY = y;
673 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
674 }
675 } break;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700676 case MotionEvent.ACTION_UP: {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700677 float x = event.getX();
678 float y = event.getY();
679 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
680
681 Gesture gesture = new Gesture();
682 gesture.addStroke(new GestureStroke(mStrokeBuffer));
683
684 ArrayList<Prediction> predictions = mGestureLibrary.recognize(gesture);
685 if (!predictions.isEmpty()) {
686 Prediction bestPrediction = predictions.get(0);
687 if (bestPrediction.score >= MIN_PREDICTION_SCORE) {
688 if (DEBUG) {
689 Slog.i(LOG_TAG, "gesture: " + bestPrediction.name + " score: "
690 + bestPrediction.score);
691 }
692 try {
693 final int gestureId = Integer.parseInt(bestPrediction.name);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700694 mAms.onGesture(gestureId);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700695 } catch (NumberFormatException nfe) {
696 Slog.w(LOG_TAG, "Non numeric gesture id:" + bestPrediction.name);
697 }
698 }
699 }
700
701 mStrokeBuffer.clear();
702 mCurrentState = STATE_TOUCH_EXPLORING;
703 } break;
704 case MotionEvent.ACTION_CANCEL: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700705 clear(event, policyFlags);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700706 } break;
707 }
708 }
709
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700710 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700711 * Sends down events to the view hierarchy for all active pointers which are
712 * not already being delivered i.e. pointers that are not yet injected.
713 *
714 * @param prototype The prototype from which to create the injected events.
715 * @param policyFlags The policy flags associated with the event.
716 */
717 private void sendDownForAllActiveNotInjectedPointers(MotionEvent prototype, int policyFlags) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700718 ReceivedPointerTracker receivedPointers = mReceivedPointerTracker;
719 InjectedPointerTracker injectedPointers = mInjectedPointerTracker;
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700720 int pointerIdBits = 0;
721 final int pointerCount = prototype.getPointerCount();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700722
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700723 // Find which pointers are already injected.
724 for (int i = 0; i < pointerCount; i++) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700725 final int pointerId = prototype.getPointerId(i);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700726 if (injectedPointers.isInjectedPointerDown(pointerId)) {
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700727 pointerIdBits |= (1 << pointerId);
728 }
729 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700730
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700731 // Inject the active and not injected pointers.
732 for (int i = 0; i < pointerCount; i++) {
733 final int pointerId = prototype.getPointerId(i);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700734 // Skip inactive pointers.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700735 if (!receivedPointers.isActivePointer(pointerId)) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700736 continue;
737 }
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700738 // Do not send event for already delivered pointers.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700739 if (injectedPointers.isInjectedPointerDown(pointerId)) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700740 continue;
741 }
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700742 pointerIdBits |= (1 << pointerId);
743 final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
744 sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
745 }
746 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700747
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700748 /**
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700749 * Sends the exit events if needed. Such events are hover exit and touch explore
750 * gesture end.
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700751 *
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700752 * @param policyFlags The policy flags associated with the event.
753 */
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700754 private void sendExitEventsIfNeeded(int policyFlags) {
755 MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
756 if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
757 final int pointerIdBits = event.getPointerIdBits();
758 mAms.touchExplorationGestureEnded();
759 sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags);
760 }
761 }
762
763 /**
764 * Sends the enter events if needed. Such events are hover enter and touch explore
765 * gesture start.
766 *
767 * @param policyFlags The policy flags associated with the event.
768 */
769 private void sendEnterEventsIfNeeded(int policyFlags) {
770 MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
771 if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
772 final int pointerIdBits = event.getPointerIdBits();
773 mAms.touchExplorationGestureStarted();
774 sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700775 }
776 }
777
778 /**
779 * Sends up events to the view hierarchy for all active pointers which are
780 * already being delivered i.e. pointers that are injected.
781 *
782 * @param prototype The prototype from which to create the injected events.
783 * @param policyFlags The policy flags associated with the event.
784 */
785 private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700786 final InjectedPointerTracker injectedTracked = mInjectedPointerTracker;
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700787 int pointerIdBits = 0;
788 final int pointerCount = prototype.getPointerCount();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700789 for (int i = 0; i < pointerCount; i++) {
790 final int pointerId = prototype.getPointerId(i);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700791 // Skip non injected down pointers.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700792 if (!injectedTracked.isInjectedPointerDown(pointerId)) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700793 continue;
794 }
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700795 pointerIdBits |= (1 << pointerId);
796 final int action = computeInjectionAction(MotionEvent.ACTION_UP, i);
797 sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700798 }
799 }
800
801 /**
802 * Sends a motion event by first stripping the inactive pointers.
803 *
804 * @param prototype The prototype from which to create the injected event.
805 * @param policyFlags The policy flags associated with the event.
806 */
807 private void sendMotionEventStripInactivePointers(MotionEvent prototype, int policyFlags) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700808 ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700809
810 // All pointers active therefore we just inject the event as is.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700811 if (prototype.getPointerCount() == receivedTracker.getActivePointerCount()) {
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700812 sendMotionEvent(prototype, prototype.getAction(), ALL_POINTER_ID_BITS, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700813 return;
814 }
815
816 // No active pointers and the one that just went up was not
817 // active, therefore we have nothing to do.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700818 if (receivedTracker.getActivePointerCount() == 0
819 && !receivedTracker.wasLastReceivedUpPointerActive()) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700820 return;
821 }
822
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700823 // If the action pointer going up/down is not active we have nothing to do.
824 // However, for moves we keep going to report moves of active pointers.
825 final int actionMasked = prototype.getActionMasked();
826 final int actionPointerId = prototype.getPointerId(prototype.getActionIndex());
827 if (actionMasked != MotionEvent.ACTION_MOVE) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700828 if (!receivedTracker.isActiveOrWasLastActiveUpPointer(actionPointerId)) {
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700829 return;
830 }
831 }
832
833 // If the pointer is active or the pointer that just went up
834 // was active we keep the pointer data in the event.
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700835 int pointerIdBits = 0;
836 final int pointerCount = prototype.getPointerCount();
837 for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) {
838 final int pointerId = prototype.getPointerId(pointerIndex);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700839 if (receivedTracker.isActiveOrWasLastActiveUpPointer(pointerId)) {
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700840 pointerIdBits |= (1 << pointerId);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700841 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700842 }
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700843 sendMotionEvent(prototype, prototype.getAction(), pointerIdBits, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700844 }
845
846 /**
847 * Sends an up and down events.
848 *
849 * @param prototype The prototype from which to create the injected events.
850 * @param policyFlags The policy flags associated with the event.
851 */
852 private void sendActionDownAndUp(MotionEvent prototype, int policyFlags) {
Svetoslav Ganovbd206d12011-09-15 17:33:07 -0700853 // Tap with the pointer that last explored - we may have inactive pointers.
854 final int pointerId = prototype.getPointerId(prototype.getActionIndex());
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700855 final int pointerIdBits = (1 << pointerId);
856 sendMotionEvent(prototype, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags);
857 sendMotionEvent(prototype, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700858 }
859
860 /**
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700861 * Sends an event.
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700862 *
Svetoslav Ganov00f7b3f2011-06-07 19:24:10 -0700863 * @param prototype The prototype from which to create the injected events.
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700864 * @param action The action of the event.
865 * @param pointerIdBits The bits of the pointers to send.
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700866 * @param policyFlags The policy flags associated with the event.
867 */
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700868 private void sendMotionEvent(MotionEvent prototype, int action, int pointerIdBits,
869 int policyFlags) {
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700870 prototype.setAction(action);
871
872 MotionEvent event = null;
873 if (pointerIdBits == ALL_POINTER_ID_BITS) {
874 event = prototype;
875 } else {
876 event = prototype.split(pointerIdBits);
877 }
878 if (action == MotionEvent.ACTION_DOWN) {
879 event.setDownTime(event.getEventTime());
880 } else {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700881 event.setDownTime(mInjectedPointerTracker.getLastInjectedDownEventTime());
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700882 }
883
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700884 // If the user is long pressing but the long pressing pointer
885 // was not exactly over the accessibility focused item we need
886 // to remap the location of that pointer so the user does not
887 // have to explicitly touch explore something to be able to
888 // long press it, or even worse to avoid the user long pressing
889 // on the wrong item since click and long press behave differently.
890 if (mLongPressingPointerId >= 0) {
891 final int remappedIndex = event.findPointerIndex(mLongPressingPointerId);
892 final int pointerCount = event.getPointerCount();
893 PointerProperties[] props = PointerProperties.createArray(pointerCount);
894 PointerCoords[] coords = PointerCoords.createArray(pointerCount);
895 for (int i = 0; i < pointerCount; i++) {
896 event.getPointerProperties(i, props[i]);
897 event.getPointerCoords(i, coords[i]);
898 if (i == remappedIndex) {
899 coords[i].x -= mLongPressingPointerDeltaX;
900 coords[i].y -= mLongPressingPointerDeltaY;
901 }
902 }
903 MotionEvent remapped = MotionEvent.obtain(event.getDownTime(),
904 event.getEventTime(), event.getAction(), event.getPointerCount(),
905 props, coords, event.getMetaState(), event.getButtonState(),
906 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(),
907 event.getSource(), event.getFlags());
908 if (event != prototype) {
909 event.recycle();
910 }
911 event = remapped;
912 }
913
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700914 if (DEBUG) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700915 Slog.d(LOG_TAG, "Injecting event: " + event + ", policyFlags=0x"
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700916 + Integer.toHexString(policyFlags));
917 }
918
919 // Make sure that the user will see the event.
920 policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700921 mInputFilter.sendInputEvent(event, policyFlags);
922
Svetoslav Ganov42138042012-03-20 11:51:39 -0700923 mInjectedPointerTracker.onMotionEvent(event);
924
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700925 if (event != prototype) {
926 event.recycle();
927 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700928 }
929
930 /**
931 * Computes the action for an injected event based on a masked action
932 * and a pointer index.
933 *
934 * @param actionMasked The masked action.
935 * @param pointerIndex The index of the pointer which has changed.
936 * @return The action to be used for injection.
937 */
938 private int computeInjectionAction(int actionMasked, int pointerIndex) {
939 switch (actionMasked) {
940 case MotionEvent.ACTION_DOWN:
941 case MotionEvent.ACTION_POINTER_DOWN: {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700942 InjectedPointerTracker injectedTracker = mInjectedPointerTracker;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700943 // Compute the action based on how many down pointers are injected.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700944 if (injectedTracker.getInjectedPointerDownCount() == 0) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700945 return MotionEvent.ACTION_DOWN;
946 } else {
947 return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
948 | MotionEvent.ACTION_POINTER_DOWN;
949 }
950 }
951 case MotionEvent.ACTION_POINTER_UP: {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700952 InjectedPointerTracker injectedTracker = mInjectedPointerTracker;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700953 // Compute the action based on how many down pointers are injected.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700954 if (injectedTracker.getInjectedPointerDownCount() == 1) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700955 return MotionEvent.ACTION_UP;
956 } else {
957 return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
958 | MotionEvent.ACTION_POINTER_UP;
959 }
960 }
961 default:
962 return actionMasked;
963 }
964 }
965
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700966 private class DoubleTapDetector {
967 private MotionEvent mDownEvent;
968 private MotionEvent mFirstTapEvent;
969
970 public void onMotionEvent(MotionEvent event, int policyFlags) {
971 final int action = event.getActionMasked();
972 switch (action) {
973 case MotionEvent.ACTION_DOWN:
974 case MotionEvent.ACTION_POINTER_DOWN: {
975 if (mFirstTapEvent != null && !isSamePointerContext(mFirstTapEvent, event)) {
976 clear();
977 }
978 mDownEvent = MotionEvent.obtain(event);
979 } break;
980 case MotionEvent.ACTION_UP:
981 case MotionEvent.ACTION_POINTER_UP: {
982 if (mDownEvent == null) {
983 return;
984 }
985 if (!isSamePointerContext(mDownEvent, event)) {
986 clear();
987 return;
988 }
989 if (isTap(mDownEvent, event)) {
990 if (mFirstTapEvent == null || isTimedOut(mFirstTapEvent, event,
991 mDoubleTapTimeout)) {
992 mFirstTapEvent = MotionEvent.obtain(event);
993 mDownEvent.recycle();
994 mDownEvent = null;
995 return;
996 }
997 if (isDoubleTap(mFirstTapEvent, event)) {
998 onDoubleTap(event, policyFlags);
999 mFirstTapEvent.recycle();
1000 mFirstTapEvent = null;
1001 mDownEvent.recycle();
1002 mDownEvent = null;
1003 return;
1004 }
1005 mFirstTapEvent.recycle();
1006 mFirstTapEvent = null;
1007 } else {
1008 if (mFirstTapEvent != null) {
1009 mFirstTapEvent.recycle();
1010 mFirstTapEvent = null;
1011 }
1012 }
1013 mDownEvent.recycle();
1014 mDownEvent = null;
1015 } break;
1016 }
1017 }
1018
1019 public void onDoubleTap(MotionEvent secondTapUp, int policyFlags) {
1020 // This should never be called when more than two pointers are down.
1021 if (secondTapUp.getPointerCount() > 2) {
1022 return;
1023 }
1024
1025 // Remove pending event deliveries.
1026 mSendHoverEnterDelayed.remove();
1027 mSendHoverExitDelayed.remove();
1028 mPerformLongPressDelayed.remove();
1029
1030 // This is a tap so do not send hover events since
1031 // this events will result in firing the corresponding
1032 // accessibility events confusing the user about what
1033 // is actually clicked.
1034 sendExitEventsIfNeeded(policyFlags);
1035
1036 // If the last touched explored location is not within the focused
1037 // window we will click at that exact spot, otherwise we find the
1038 // accessibility focus and if the tap is within its bounds we click
1039 // there, otherwise we pick the middle of the focus rectangle.
1040 MotionEvent lastEvent = mInjectedPointerTracker.getLastInjectedHoverEvent();
1041 if (lastEvent == null) {
1042 return;
1043 }
1044
1045 final int exploreLocationX = (int) lastEvent.getX(lastEvent.getActionIndex());
1046 final int exploreLocationY = (int) lastEvent.getY(lastEvent.getActionIndex());
1047
1048 Rect bounds = mTempRect;
1049 boolean useLastHoverLocation = false;
1050
1051 final int pointerId = secondTapUp.getPointerId(secondTapUp.getActionIndex());
1052 final int pointerIndex = secondTapUp.findPointerIndex(pointerId);
1053 if (mAms.getAccessibilityFocusBounds(exploreLocationX, exploreLocationY, bounds)) {
1054 // If the user's last touch explored location is not
1055 // within the accessibility focus bounds we use the center
1056 // of the accessibility focused rectangle.
1057 if (!bounds.contains((int) secondTapUp.getX(pointerIndex),
1058 (int) secondTapUp.getY(pointerIndex))) {
1059 useLastHoverLocation = true;
1060 }
1061 }
1062
1063 // Do the click.
1064 PointerProperties[] properties = new PointerProperties[1];
1065 properties[0] = new PointerProperties();
1066 secondTapUp.getPointerProperties(pointerIndex, properties[0]);
1067 PointerCoords[] coords = new PointerCoords[1];
1068 coords[0] = new PointerCoords();
1069 coords[0].x = (useLastHoverLocation) ? bounds.centerX() : exploreLocationX;
1070 coords[0].y = (useLastHoverLocation) ? bounds.centerY() : exploreLocationY;
1071 MotionEvent event = MotionEvent.obtain(secondTapUp.getDownTime(),
1072 secondTapUp.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties,
1073 coords, 0, 0, 1.0f, 1.0f, secondTapUp.getDeviceId(), 0,
1074 secondTapUp.getSource(), secondTapUp.getFlags());
1075 sendActionDownAndUp(event, policyFlags);
1076 event.recycle();
1077 }
1078
1079 public void clear() {
1080 if (mDownEvent != null) {
1081 mDownEvent.recycle();
1082 mDownEvent = null;
1083 }
1084 if (mFirstTapEvent != null) {
1085 mFirstTapEvent.recycle();
1086 mFirstTapEvent = null;
1087 }
1088 }
1089
1090 public boolean isTap(MotionEvent down, MotionEvent up) {
1091 return eventsWithinTimeoutAndDistance(down, up, mTapTimeout, mTouchSlop);
1092 }
1093
1094 private boolean isDoubleTap(MotionEvent firstUp, MotionEvent secondUp) {
1095 return eventsWithinTimeoutAndDistance(firstUp, secondUp, mDoubleTapTimeout,
1096 mDoubleTapSlop);
1097 }
1098
1099 private boolean eventsWithinTimeoutAndDistance(MotionEvent first, MotionEvent second,
1100 int timeout, int distance) {
1101 if (isTimedOut(first, second, timeout)) {
1102 return false;
1103 }
1104 final int downPtrIndex = first.getActionIndex();
1105 final int upPtrIndex = second.getActionIndex();
1106 final float deltaX = second.getX(upPtrIndex) - first.getX(downPtrIndex);
1107 final float deltaY = second.getY(upPtrIndex) - first.getY(downPtrIndex);
1108 final double deltaMove = Math.hypot(deltaX, deltaY);
1109 if (deltaMove >= distance) {
1110 return false;
1111 }
1112 return true;
1113 }
1114
1115 private boolean isTimedOut(MotionEvent firstUp, MotionEvent secondUp, int timeout) {
1116 final long deltaTime = secondUp.getEventTime() - firstUp.getEventTime();
1117 return (deltaTime >= timeout);
1118 }
1119
1120 private boolean isSamePointerContext(MotionEvent first, MotionEvent second) {
1121 return (first.getPointerIdBits() == second.getPointerIdBits()
1122 && first.getPointerId(first.getActionIndex())
1123 == second.getPointerId(second.getActionIndex()));
1124 }
1125
1126 public boolean firstTapDetected() {
1127 return mFirstTapEvent != null
1128 && SystemClock.uptimeMillis() - mFirstTapEvent.getEventTime() < mDoubleTapTimeout;
1129 }
1130 }
1131
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001132 /**
1133 * Determines whether a two pointer gesture is a dragging one.
1134 *
1135 * @param event The event with the pointer data.
1136 * @return True if the gesture is a dragging one.
1137 */
1138 private boolean isDraggingGesture(MotionEvent event) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001139 ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001140 int[] pointerIds = mTempPointerIds;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001141 receivedTracker.populateActivePointerIds(pointerIds);
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001142
1143 final int firstPtrIndex = event.findPointerIndex(pointerIds[0]);
1144 final int secondPtrIndex = event.findPointerIndex(pointerIds[1]);
1145
1146 final float firstPtrX = event.getX(firstPtrIndex);
1147 final float firstPtrY = event.getY(firstPtrIndex);
1148 final float secondPtrX = event.getX(secondPtrIndex);
1149 final float secondPtrY = event.getY(secondPtrIndex);
1150
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001151 // Check if the pointers are moving in the same direction.
1152 final float firstDeltaX =
Svetoslav Ganov42138042012-03-20 11:51:39 -07001153 firstPtrX - receivedTracker.getReceivedPointerDownX(firstPtrIndex);
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001154 final float firstDeltaY =
Svetoslav Ganov42138042012-03-20 11:51:39 -07001155 firstPtrY - receivedTracker.getReceivedPointerDownY(firstPtrIndex);
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001156
1157 if (firstDeltaX == 0 && firstDeltaY == 0) {
1158 return true;
1159 }
1160
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001161 final float firstMagnitude =
1162 (float) Math.sqrt(firstDeltaX * firstDeltaX + firstDeltaY * firstDeltaY);
1163 final float firstXNormalized =
1164 (firstMagnitude > 0) ? firstDeltaX / firstMagnitude : firstDeltaX;
1165 final float firstYNormalized =
1166 (firstMagnitude > 0) ? firstDeltaY / firstMagnitude : firstDeltaY;
1167
1168 final float secondDeltaX =
Svetoslav Ganov42138042012-03-20 11:51:39 -07001169 secondPtrX - receivedTracker.getReceivedPointerDownX(secondPtrIndex);
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001170 final float secondDeltaY =
Svetoslav Ganov42138042012-03-20 11:51:39 -07001171 secondPtrY - receivedTracker.getReceivedPointerDownY(secondPtrIndex);
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001172
1173 if (secondDeltaX == 0 && secondDeltaY == 0) {
1174 return true;
1175 }
1176
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001177 final float secondMagnitude =
1178 (float) Math.sqrt(secondDeltaX * secondDeltaX + secondDeltaY * secondDeltaY);
1179 final float secondXNormalized =
1180 (secondMagnitude > 0) ? secondDeltaX / secondMagnitude : secondDeltaX;
1181 final float secondYNormalized =
1182 (secondMagnitude > 0) ? secondDeltaY / secondMagnitude : secondDeltaY;
1183
1184 final float angleCos =
1185 firstXNormalized * secondXNormalized + firstYNormalized * secondYNormalized;
1186
Svetoslav Ganov12a024c2011-09-03 19:52:36 -07001187 if (angleCos < MAX_DRAGGING_ANGLE_COS) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001188 return false;
1189 }
1190
1191 return true;
1192 }
1193
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001194 /**
Svetoslav Ganov51cccf02011-06-27 12:00:54 -07001195 * Gets the symbolic name of a state.
1196 *
1197 * @param state A state.
1198 * @return The state symbolic name.
1199 */
1200 private static String getStateSymbolicName(int state) {
1201 switch (state) {
1202 case STATE_TOUCH_EXPLORING:
1203 return "STATE_TOUCH_EXPLORING";
1204 case STATE_DRAGGING:
1205 return "STATE_DRAGGING";
1206 case STATE_DELEGATING:
1207 return "STATE_DELEGATING";
Svetoslav Ganov42138042012-03-20 11:51:39 -07001208 case STATE_GESTURE_DETECTING:
1209 return "STATE_GESTURE_DETECTING";
Svetoslav Ganov51cccf02011-06-27 12:00:54 -07001210 default:
1211 throw new IllegalArgumentException("Unknown state: " + state);
1212 }
1213 }
1214
1215 /**
Svetoslav Ganov42138042012-03-20 11:51:39 -07001216 * @return The number of non injected active pointers.
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001217 */
Svetoslav Ganov42138042012-03-20 11:51:39 -07001218 private int getNotInjectedActivePointerCount(ReceivedPointerTracker receivedTracker,
1219 InjectedPointerTracker injectedTracker) {
1220 final int pointerState = receivedTracker.getActivePointers()
1221 & ~injectedTracker.getInjectedPointersDown();
1222 return Integer.bitCount(pointerState);
1223 }
1224
1225 /**
1226 * Class for delayed sending of long press.
1227 */
1228 private final class PerformLongPressDelayed implements Runnable {
1229 private MotionEvent mEvent;
1230 private int mPolicyFlags;
1231
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001232 public void post(MotionEvent prototype, int policyFlags) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001233 mEvent = MotionEvent.obtain(prototype);
1234 mPolicyFlags = policyFlags;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001235 mHandler.postDelayed(this, ViewConfiguration.getLongPressTimeout());
Svetoslav Ganov42138042012-03-20 11:51:39 -07001236 }
1237
1238 public void remove() {
1239 if (isPenidng()) {
1240 mHandler.removeCallbacks(this);
1241 clear();
1242 }
1243 }
1244
1245 private boolean isPenidng() {
1246 return (mEvent != null);
1247 }
1248
1249 @Override
1250 public void run() {
Svetoslav Ganovebac1b72012-06-02 16:26:44 -07001251 // Active pointers should not be zero when running this command.
1252 if (mReceivedPointerTracker.getActivePointerCount() == 0) {
1253 return;
1254 }
1255
Svetoslav Ganov238099c2012-06-01 13:52:54 -07001256 // If the last touched explored location is not within the focused
1257 // window we will long press at that exact spot, otherwise we find the
1258 // accessibility focus and if the tap is within its bounds we long press
1259 // there, otherwise we pick the middle of the focus rectangle.
1260 MotionEvent lastEvent = mInjectedPointerTracker.getLastInjectedHoverEvent();
1261 if (lastEvent == null) {
1262 return;
1263 }
1264
1265 final int exploreLocationX = (int) lastEvent.getX(lastEvent.getActionIndex());
1266 final int exploreLocationY = (int) lastEvent.getY(lastEvent.getActionIndex());
1267
1268 Rect bounds = mTempRect;
1269 boolean useFocusedBounds = false;
1270
1271 final int pointerId = mEvent.getPointerId(mEvent.getActionIndex());
1272 final int pointerIndex = mEvent.findPointerIndex(pointerId);
1273 if (mAms.getAccessibilityFocusBounds(exploreLocationX, exploreLocationY, bounds)) {
1274 // If the user's last touch explored location is not
1275 // within the accessibility focus bounds we use the center
1276 // of the accessibility focused rectangle.
1277 if (!bounds.contains((int) mEvent.getX(pointerIndex),
1278 (int) mEvent.getY(pointerIndex))) {
1279 useFocusedBounds = true;
1280 }
1281 }
1282
1283 mLongPressingPointerId = mEvent.getPointerId(pointerIndex);
1284
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001285 final int eventX = (int) mEvent.getX(pointerIndex);
1286 final int eventY = (int) mEvent.getY(pointerIndex);
Svetoslav Ganov238099c2012-06-01 13:52:54 -07001287 if (useFocusedBounds) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001288 mLongPressingPointerDeltaX = eventX - bounds.centerX();
1289 mLongPressingPointerDeltaY = eventY - bounds.centerY();
1290 } else {
Svetoslav Ganov238099c2012-06-01 13:52:54 -07001291 mLongPressingPointerDeltaX = eventX - exploreLocationX;
1292 mLongPressingPointerDeltaY = eventY - exploreLocationY;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001293 }
Svetoslav Ganov238099c2012-06-01 13:52:54 -07001294
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001295 sendExitEventsIfNeeded(mPolicyFlags);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001296
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001297 mCurrentState = STATE_DELEGATING;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001298 sendDownForAllActiveNotInjectedPointers(mEvent, mPolicyFlags);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001299 clear();
1300 }
1301
1302 private void clear() {
1303 if (!isPenidng()) {
1304 return;
1305 }
1306 mEvent.recycle();
1307 mEvent = null;
1308 mPolicyFlags = 0;
1309 }
1310 }
1311
1312 /**
1313 * Class for delayed sending of hover events.
1314 */
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001315 class SendHoverDelayed implements Runnable {
1316 private final String LOG_TAG_SEND_HOVER_DELAYED = SendHoverDelayed.class.getName();
1317
1318 private final int mHoverAction;
1319 private final boolean mGestureStarted;
1320
1321 private MotionEvent mPrototype;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001322 private int mPointerIdBits;
1323 private int mPolicyFlags;
1324
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001325 public SendHoverDelayed(int hoverAction, boolean gestureStarted) {
1326 mHoverAction = hoverAction;
1327 mGestureStarted = gestureStarted;
1328 }
1329
1330 public void post(MotionEvent prototype, int pointerIdBits, int policyFlags) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001331 remove();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001332 mPrototype = MotionEvent.obtain(prototype);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001333 mPointerIdBits = pointerIdBits;
1334 mPolicyFlags = policyFlags;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001335 mHandler.postDelayed(this, mTapTimeout);
1336 }
1337
1338 public float getX() {
1339 if (isPending()) {
1340 return mPrototype.getX();
1341 }
1342 return 0;
1343 }
1344
1345 public float getY() {
1346 if (isPending()) {
1347 return mPrototype.getY();
1348 }
1349 return 0;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001350 }
1351
1352 public void remove() {
1353 mHandler.removeCallbacks(this);
1354 clear();
1355 }
1356
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001357 private boolean isPending() {
1358 return (mPrototype != null);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001359 }
1360
1361 private void clear() {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001362 if (!isPending()) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001363 return;
1364 }
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001365 mPrototype.recycle();
1366 mPrototype = null;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001367 mPointerIdBits = -1;
1368 mPolicyFlags = 0;
1369 }
1370
1371 public void forceSendAndRemove() {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001372 if (isPending()) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001373 run();
1374 remove();
1375 }
1376 }
1377
1378 public void run() {
1379 if (DEBUG) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001380 Slog.d(LOG_TAG_SEND_HOVER_DELAYED, "Injecting motion event: "
1381 + MotionEvent.actionToString(mHoverAction));
1382 Slog.d(LOG_TAG_SEND_HOVER_DELAYED, mGestureStarted ?
1383 "touchExplorationGestureStarted" : "touchExplorationGestureEnded");
Svetoslav Ganov42138042012-03-20 11:51:39 -07001384 }
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001385 if (mGestureStarted) {
1386 mAms.touchExplorationGestureStarted();
1387 } else {
1388 mAms.touchExplorationGestureEnded();
1389 }
1390 sendMotionEvent(mPrototype, mHoverAction, mPointerIdBits, mPolicyFlags);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001391 clear();
1392 }
1393 }
1394
1395 @Override
1396 public String toString() {
1397 return LOG_TAG;
1398 }
1399
1400 class InjectedPointerTracker {
1401 private static final String LOG_TAG_INJECTED_POINTER_TRACKER = "InjectedPointerTracker";
1402
1403 // Keep track of which pointers sent to the system are down.
1404 private int mInjectedPointersDown;
1405
1406 // The time of the last injected down.
1407 private long mLastInjectedDownEventTime;
1408
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001409 // The last injected hover event.
1410 private MotionEvent mLastInjectedHoverEvent;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001411
1412 /**
1413 * Processes an injected {@link MotionEvent} event.
1414 *
1415 * @param event The event to process.
1416 */
1417 public void onMotionEvent(MotionEvent event) {
1418 final int action = event.getActionMasked();
1419 switch (action) {
1420 case MotionEvent.ACTION_DOWN:
1421 case MotionEvent.ACTION_POINTER_DOWN: {
1422 final int pointerId = event.getPointerId(event.getActionIndex());
1423 final int pointerFlag = (1 << pointerId);
1424 mInjectedPointersDown |= pointerFlag;
1425 mLastInjectedDownEventTime = event.getDownTime();
1426 } break;
1427 case MotionEvent.ACTION_UP:
1428 case MotionEvent.ACTION_POINTER_UP: {
1429 final int pointerId = event.getPointerId(event.getActionIndex());
1430 final int pointerFlag = (1 << pointerId);
1431 mInjectedPointersDown &= ~pointerFlag;
1432 if (mInjectedPointersDown == 0) {
1433 mLastInjectedDownEventTime = 0;
1434 }
1435 } break;
1436 case MotionEvent.ACTION_HOVER_ENTER:
1437 case MotionEvent.ACTION_HOVER_MOVE:
1438 case MotionEvent.ACTION_HOVER_EXIT: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001439 if (mLastInjectedHoverEvent != null) {
1440 mLastInjectedHoverEvent.recycle();
1441 }
1442 mLastInjectedHoverEvent = MotionEvent.obtain(event);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001443 } break;
1444 }
1445 if (DEBUG) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001446 Slog.i(LOG_TAG_INJECTED_POINTER_TRACKER, "Injected pointer:\n" + toString());
Svetoslav Ganov42138042012-03-20 11:51:39 -07001447 }
1448 }
1449
1450 /**
1451 * Clears the internals state.
1452 */
1453 public void clear() {
1454 mInjectedPointersDown = 0;
1455 }
1456
1457 /**
1458 * @return The time of the last injected down event.
1459 */
1460 public long getLastInjectedDownEventTime() {
1461 return mLastInjectedDownEventTime;
1462 }
1463
1464 /**
1465 * @return The number of down pointers injected to the view hierarchy.
1466 */
1467 public int getInjectedPointerDownCount() {
1468 return Integer.bitCount(mInjectedPointersDown);
1469 }
1470
1471 /**
1472 * @return The bits of the injected pointers that are down.
1473 */
1474 public int getInjectedPointersDown() {
1475 return mInjectedPointersDown;
1476 }
1477
1478 /**
1479 * Whether an injected pointer is down.
1480 *
1481 * @param pointerId The unique pointer id.
1482 * @return True if the pointer is down.
1483 */
1484 public boolean isInjectedPointerDown(int pointerId) {
1485 final int pointerFlag = (1 << pointerId);
1486 return (mInjectedPointersDown & pointerFlag) != 0;
1487 }
1488
1489 /**
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001490 * @return The the last injected hover event.
Svetoslav Ganov42138042012-03-20 11:51:39 -07001491 */
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001492 public MotionEvent getLastInjectedHoverEvent() {
1493 return mLastInjectedHoverEvent;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001494 }
1495
1496 @Override
1497 public String toString() {
1498 StringBuilder builder = new StringBuilder();
1499 builder.append("=========================");
1500 builder.append("\nDown pointers #");
1501 builder.append(Integer.bitCount(mInjectedPointersDown));
1502 builder.append(" [ ");
1503 for (int i = 0; i < MAX_POINTER_COUNT; i++) {
1504 if ((mInjectedPointersDown & i) != 0) {
1505 builder.append(i);
1506 builder.append(" ");
1507 }
1508 }
1509 builder.append("]");
1510 builder.append("\n=========================");
1511 return builder.toString();
1512 }
1513 }
1514
1515 class ReceivedPointerTracker {
1516 private static final String LOG_TAG_RECEIVED_POINTER_TRACKER = "ReceivedPointerTracker";
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001517
1518 // The coefficient by which to multiply
1519 // ViewConfiguration.#getScaledTouchSlop()
1520 // to compute #mThresholdActivePointer.
1521 private static final int COEFFICIENT_ACTIVE_POINTER = 2;
1522
1523 // Pointers that moved less than mThresholdActivePointer
1524 // are considered active i.e. are ignored.
1525 private final double mThresholdActivePointer;
1526
1527 // Keep track of where and when a pointer went down.
1528 private final float[] mReceivedPointerDownX = new float[MAX_POINTER_COUNT];
1529 private final float[] mReceivedPointerDownY = new float[MAX_POINTER_COUNT];
1530 private final long[] mReceivedPointerDownTime = new long[MAX_POINTER_COUNT];
1531
1532 // Which pointers are down.
1533 private int mReceivedPointersDown;
1534
1535 // Which down pointers are active.
1536 private int mActivePointers;
1537
1538 // Primary active pointer which is either the first that went down
1539 // or if it goes up the next active that most recently went down.
1540 private int mPrimaryActivePointerId;
1541
1542 // Flag indicating that there is at least one active pointer moving.
1543 private boolean mHasMovingActivePointer;
1544
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001545 // Keep track of the last up pointer data.
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001546 private long mLastReceivedUpPointerDownTime;
1547 private int mLastReceivedUpPointerId;
1548 private boolean mLastReceivedUpPointerActive;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001549 private float mLastReceivedUpPointerDownX;
1550 private float mLastReceivedUpPointerDownY;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001551
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001552 private MotionEvent mLastReceivedEvent;
1553
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001554 /**
1555 * Creates a new instance.
1556 *
1557 * @param context Context for looking up resources.
1558 */
Svetoslav Ganov42138042012-03-20 11:51:39 -07001559 public ReceivedPointerTracker(Context context) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001560 mThresholdActivePointer =
Svetoslav Ganov9a4c5cd2012-05-30 14:06:32 -07001561 ViewConfiguration.get(context).getScaledTouchSlop() * COEFFICIENT_ACTIVE_POINTER;//Heie govna
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001562 }
1563
1564 /**
1565 * Clears the internals state.
1566 */
1567 public void clear() {
1568 Arrays.fill(mReceivedPointerDownX, 0);
1569 Arrays.fill(mReceivedPointerDownY, 0);
1570 Arrays.fill(mReceivedPointerDownTime, 0);
1571 mReceivedPointersDown = 0;
1572 mActivePointers = 0;
1573 mPrimaryActivePointerId = 0;
1574 mHasMovingActivePointer = false;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001575 mLastReceivedUpPointerDownTime = 0;
1576 mLastReceivedUpPointerId = 0;
1577 mLastReceivedUpPointerActive = false;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001578 mLastReceivedUpPointerDownX = 0;
1579 mLastReceivedUpPointerDownY = 0;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001580 }
1581
1582 /**
1583 * Processes a received {@link MotionEvent} event.
1584 *
1585 * @param event The event to process.
1586 */
Svetoslav Ganov42138042012-03-20 11:51:39 -07001587 public void onMotionEvent(MotionEvent event) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001588 if (mLastReceivedEvent != null) {
1589 mLastReceivedEvent.recycle();
1590 }
1591 mLastReceivedEvent = MotionEvent.obtain(event);
1592
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001593 final int action = event.getActionMasked();
1594 switch (action) {
1595 case MotionEvent.ACTION_DOWN: {
Svetoslav Ganov00f7b3f2011-06-07 19:24:10 -07001596 handleReceivedPointerDown(event.getActionIndex(), event);
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001597 } break;
1598 case MotionEvent.ACTION_POINTER_DOWN: {
1599 handleReceivedPointerDown(event.getActionIndex(), event);
1600 } break;
1601 case MotionEvent.ACTION_MOVE: {
1602 handleReceivedPointerMove(event);
1603 } break;
1604 case MotionEvent.ACTION_UP: {
Svetoslav Ganov00f7b3f2011-06-07 19:24:10 -07001605 handleReceivedPointerUp(event.getActionIndex(), event);
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001606 } break;
1607 case MotionEvent.ACTION_POINTER_UP: {
1608 handleReceivedPointerUp(event.getActionIndex(), event);
1609 } break;
1610 }
1611 if (DEBUG) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001612 Slog.i(LOG_TAG_RECEIVED_POINTER_TRACKER, "Received pointer: " + toString());
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001613 }
1614 }
1615
1616 /**
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001617 * @return The last received event.
1618 */
1619 public MotionEvent getLastReceivedEvent() {
1620 return mLastReceivedEvent;
1621 }
1622
1623 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001624 * @return The number of received pointers that are down.
1625 */
1626 public int getReceivedPointerDownCount() {
1627 return Integer.bitCount(mReceivedPointersDown);
1628 }
1629
1630 /**
Svetoslav Ganov42138042012-03-20 11:51:39 -07001631 * @return The bits of the pointers that are active.
1632 */
1633 public int getActivePointers() {
1634 return mActivePointers;
1635 }
1636
1637 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001638 * @return The number of down input pointers that are active.
1639 */
1640 public int getActivePointerCount() {
1641 return Integer.bitCount(mActivePointers);
1642 }
1643
1644 /**
1645 * Whether an received pointer is down.
1646 *
1647 * @param pointerId The unique pointer id.
1648 * @return True if the pointer is down.
1649 */
1650 public boolean isReceivedPointerDown(int pointerId) {
1651 final int pointerFlag = (1 << pointerId);
1652 return (mReceivedPointersDown & pointerFlag) != 0;
1653 }
1654
1655 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001656 * Whether an input pointer is active.
1657 *
1658 * @param pointerId The unique pointer id.
1659 * @return True if the pointer is active.
1660 */
1661 public boolean isActivePointer(int pointerId) {
1662 final int pointerFlag = (1 << pointerId);
1663 return (mActivePointers & pointerFlag) != 0;
1664 }
1665
1666 /**
1667 * @param pointerId The unique pointer id.
1668 * @return The X coordinate where the pointer went down.
1669 */
1670 public float getReceivedPointerDownX(int pointerId) {
1671 return mReceivedPointerDownX[pointerId];
1672 }
1673
1674 /**
1675 * @param pointerId The unique pointer id.
1676 * @return The Y coordinate where the pointer went down.
1677 */
1678 public float getReceivedPointerDownY(int pointerId) {
1679 return mReceivedPointerDownY[pointerId];
1680 }
1681
1682 /**
1683 * @param pointerId The unique pointer id.
1684 * @return The time when the pointer went down.
1685 */
1686 public long getReceivedPointerDownTime(int pointerId) {
1687 return mReceivedPointerDownTime[pointerId];
1688 }
1689
1690 /**
1691 * @return The id of the primary pointer.
1692 */
1693 public int getPrimaryActivePointerId() {
1694 if (mPrimaryActivePointerId == INVALID_POINTER_ID) {
1695 mPrimaryActivePointerId = findPrimaryActivePointer();
1696 }
1697 return mPrimaryActivePointerId;
1698 }
1699
1700 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001701 * @return The time when the last up received pointer went down.
1702 */
1703 public long getLastReceivedUpPointerDownTime() {
1704 return mLastReceivedUpPointerDownTime;
1705 }
1706
1707 /**
1708 * @return The id of the last received pointer that went up.
1709 */
1710 public int getLastReceivedUpPointerId() {
1711 return mLastReceivedUpPointerId;
1712 }
1713
Svetoslav Ganov42138042012-03-20 11:51:39 -07001714
1715 /**
1716 * @return The down X of the last received pointer that went up.
1717 */
1718 public float getLastReceivedUpPointerDownX() {
1719 return mLastReceivedUpPointerDownX;
1720 }
1721
1722 /**
1723 * @return The down Y of the last received pointer that went up.
1724 */
1725 public float getLastReceivedUpPointerDownY() {
1726 return mLastReceivedUpPointerDownY;
1727 }
1728
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001729 /**
1730 * @return Whether the last received pointer that went up was active.
1731 */
1732 public boolean wasLastReceivedUpPointerActive() {
1733 return mLastReceivedUpPointerActive;
1734 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001735 /**
1736 * Populates the active pointer IDs to the given array.
1737 * <p>
1738 * Note: The client is responsible for providing large enough array.
1739 *
1740 * @param outPointerIds The array to which to write the active pointers.
1741 */
1742 public void populateActivePointerIds(int[] outPointerIds) {
1743 int index = 0;
1744 for (int idBits = mActivePointers; idBits != 0; ) {
1745 final int id = Integer.numberOfTrailingZeros(idBits);
1746 idBits &= ~(1 << id);
1747 outPointerIds[index] = id;
1748 index++;
1749 }
1750 }
1751
1752 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001753 * @param pointerId The unique pointer id.
1754 * @return Whether the pointer is active or was the last active than went up.
1755 */
Svetoslav Ganov42138042012-03-20 11:51:39 -07001756 public boolean isActiveOrWasLastActiveUpPointer(int pointerId) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001757 return (isActivePointer(pointerId)
1758 || (mLastReceivedUpPointerId == pointerId
1759 && mLastReceivedUpPointerActive));
1760 }
1761
1762 /**
1763 * Handles a received pointer down event.
1764 *
1765 * @param pointerIndex The index of the pointer that has changed.
1766 * @param event The event to be handled.
1767 */
1768 private void handleReceivedPointerDown(int pointerIndex, MotionEvent event) {
1769 final int pointerId = event.getPointerId(pointerIndex);
1770 final int pointerFlag = (1 << pointerId);
1771
1772 mLastReceivedUpPointerId = 0;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001773 mLastReceivedUpPointerDownTime = 0;
1774 mLastReceivedUpPointerActive = false;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001775 mLastReceivedUpPointerDownX = 0;
1776 mLastReceivedUpPointerDownX = 0;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001777
1778 mReceivedPointersDown |= pointerFlag;
1779 mReceivedPointerDownX[pointerId] = event.getX(pointerIndex);
1780 mReceivedPointerDownY[pointerId] = event.getY(pointerIndex);
1781 mReceivedPointerDownTime[pointerId] = event.getEventTime();
1782
1783 if (!mHasMovingActivePointer) {
1784 // If still no moving active pointers every
1785 // down pointer is the only active one.
1786 mActivePointers = pointerFlag;
1787 mPrimaryActivePointerId = pointerId;
1788 } else {
1789 // If at least one moving active pointer every
1790 // subsequent down pointer is active.
1791 mActivePointers |= pointerFlag;
1792 }
1793 }
1794
1795 /**
1796 * Handles a received pointer move event.
1797 *
1798 * @param event The event to be handled.
1799 */
1800 private void handleReceivedPointerMove(MotionEvent event) {
1801 detectActivePointers(event);
1802 }
1803
1804 /**
1805 * Handles a received pointer up event.
1806 *
1807 * @param pointerIndex The index of the pointer that has changed.
1808 * @param event The event to be handled.
1809 */
1810 private void handleReceivedPointerUp(int pointerIndex, MotionEvent event) {
1811 final int pointerId = event.getPointerId(pointerIndex);
1812 final int pointerFlag = (1 << pointerId);
1813
1814 mLastReceivedUpPointerId = pointerId;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001815 mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId);
1816 mLastReceivedUpPointerActive = isActivePointer(pointerId);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001817 mLastReceivedUpPointerDownX = mReceivedPointerDownX[pointerId];
1818 mLastReceivedUpPointerDownY = mReceivedPointerDownY[pointerId];
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001819
1820 mReceivedPointersDown &= ~pointerFlag;
1821 mActivePointers &= ~pointerFlag;
1822 mReceivedPointerDownX[pointerId] = 0;
1823 mReceivedPointerDownY[pointerId] = 0;
1824 mReceivedPointerDownTime[pointerId] = 0;
1825
1826 if (mActivePointers == 0) {
1827 mHasMovingActivePointer = false;
1828 }
1829 if (mPrimaryActivePointerId == pointerId) {
1830 mPrimaryActivePointerId = INVALID_POINTER_ID;
1831 }
1832 }
1833
1834 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001835 * Detects the active pointers in an event.
1836 *
1837 * @param event The event to examine.
1838 */
1839 private void detectActivePointers(MotionEvent event) {
1840 for (int i = 0, count = event.getPointerCount(); i < count; i++) {
1841 final int pointerId = event.getPointerId(i);
1842 if (mHasMovingActivePointer) {
1843 // If already active => nothing to do.
1844 if (isActivePointer(pointerId)) {
1845 continue;
1846 }
1847 }
1848 // Active pointers are ones that moved more than a given threshold.
1849 final float pointerDeltaMove = computePointerDeltaMove(i, event);
1850 if (pointerDeltaMove > mThresholdActivePointer) {
1851 final int pointerFlag = (1 << pointerId);
1852 mActivePointers |= pointerFlag;
1853 mHasMovingActivePointer = true;
1854 }
1855 }
1856 }
1857
1858 /**
1859 * @return The primary active pointer.
1860 */
1861 private int findPrimaryActivePointer() {
1862 int primaryActivePointerId = INVALID_POINTER_ID;
1863 long minDownTime = Long.MAX_VALUE;
1864 // Find the active pointer that went down first.
1865 for (int i = 0, count = mReceivedPointerDownTime.length; i < count; i++) {
1866 if (isActivePointer(i)) {
1867 final long downPointerTime = mReceivedPointerDownTime[i];
1868 if (downPointerTime < minDownTime) {
1869 minDownTime = downPointerTime;
1870 primaryActivePointerId = i;
1871 }
1872 }
1873 }
1874 return primaryActivePointerId;
1875 }
1876
1877 /**
1878 * Computes the move for a given action pointer index since the
1879 * corresponding pointer went down.
1880 *
1881 * @param pointerIndex The action pointer index.
1882 * @param event The event to examine.
1883 * @return The distance the pointer has moved.
1884 */
1885 private float computePointerDeltaMove(int pointerIndex, MotionEvent event) {
1886 final int pointerId = event.getPointerId(pointerIndex);
1887 final float deltaX = event.getX(pointerIndex) - mReceivedPointerDownX[pointerId];
1888 final float deltaY = event.getY(pointerIndex) - mReceivedPointerDownY[pointerId];
1889 return (float) Math.hypot(deltaX, deltaY);
1890 }
1891
1892 @Override
1893 public String toString() {
1894 StringBuilder builder = new StringBuilder();
1895 builder.append("=========================");
1896 builder.append("\nDown pointers #");
1897 builder.append(getReceivedPointerDownCount());
1898 builder.append(" [ ");
1899 for (int i = 0; i < MAX_POINTER_COUNT; i++) {
1900 if (isReceivedPointerDown(i)) {
1901 builder.append(i);
1902 builder.append(" ");
1903 }
1904 }
1905 builder.append("]");
1906 builder.append("\nActive pointers #");
1907 builder.append(getActivePointerCount());
1908 builder.append(" [ ");
1909 for (int i = 0; i < MAX_POINTER_COUNT; i++) {
1910 if (isActivePointer(i)) {
1911 builder.append(i);
1912 builder.append(" ");
1913 }
1914 }
1915 builder.append("]");
1916 builder.append("\nPrimary active pointer id [ ");
1917 builder.append(getPrimaryActivePointerId());
1918 builder.append(" ]");
1919 builder.append("\n=========================");
1920 return builder.toString();
1921 }
1922 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001923}