blob: b3bf6fee9c3c5bf5c7b5a32705b412da0af1df35 [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;
Casey Burkhardtea6fbc02012-06-19 16:24:10 -070024import android.gesture.GestureStore;
Svetoslav Ganov42138042012-03-20 11:51:39 -070025import android.gesture.GestureStroke;
26import android.gesture.Prediction;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -070027import android.graphics.Rect;
Svetoslav Ganov736c2752011-04-22 18:30:36 -070028import android.os.Handler;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -070029import android.os.SystemClock;
Svetoslav Ganov736c2752011-04-22 18:30:36 -070030import android.util.Slog;
Svetoslav Ganov736c2752011-04-22 18:30:36 -070031import android.view.MotionEvent;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -070032import android.view.MotionEvent.PointerCoords;
33import android.view.MotionEvent.PointerProperties;
Svetoslav Ganov42138042012-03-20 11:51:39 -070034import android.view.VelocityTracker;
Svetoslav Ganovf5a07902011-07-24 19:20:17 -070035import android.view.ViewConfiguration;
36import android.view.WindowManagerPolicy;
Svetoslav Ganov86783472012-06-06 21:12:20 -070037import android.view.accessibility.AccessibilityEvent;
Svetoslav Ganov77276b62012-09-14 10:23:00 -070038import android.view.accessibility.AccessibilityManager;
Svetoslav Ganov736c2752011-04-22 18:30:36 -070039
Svetoslav Ganov42138042012-03-20 11:51:39 -070040import com.android.internal.R;
Svetoslav Ganovf5a07902011-07-24 19:20:17 -070041
Svetoslav Ganov42138042012-03-20 11:51:39 -070042import java.util.ArrayList;
Svetoslav Ganov736c2752011-04-22 18:30:36 -070043import java.util.Arrays;
44
45/**
46 * This class is a strategy for performing touch exploration. It
47 * transforms the motion event stream by modifying, adding, replacing,
48 * and consuming certain events. The interaction model is:
49 *
50 * <ol>
Svetoslav Ganove15ccb92012-05-16 15:48:55 -070051 * <li>1. One finger moving slow around performs touch exploration.</li>
52 * <li>2. One finger moving fast around performs gestures.</li>
53 * <li>3. Two close fingers moving in the same direction perform a drag.</li>
54 * <li>4. Multi-finger gestures are delivered to view hierarchy.</li>
55 * <li>5. Pointers that have not moved more than a specified distance after they
Svetoslav Ganov736c2752011-04-22 18:30:36 -070056 * went down are considered inactive.</li>
Svetoslav Ganove15ccb92012-05-16 15:48:55 -070057 * <li>6. Two fingers moving in different directions are considered a multi-finger gesture.</li>
58 * <li>7. Double tapping clicks on the on the last touch explored location of it was in
59 * a window that does not take focus, otherwise the click is within the accessibility
60 * focused rectangle.</li>
61 * <li>7. Tapping and holding for a while performs a long press in a similar fashion
62 * as the click above.</li>
Svetoslav Ganov736c2752011-04-22 18:30:36 -070063 * <ol>
64 *
65 * @hide
66 */
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -070067class TouchExplorer implements EventStreamTransformation {
Svetoslav Ganov42138042012-03-20 11:51:39 -070068
Svetoslav Ganov736c2752011-04-22 18:30:36 -070069 private static final boolean DEBUG = false;
70
71 // Tag for logging received events.
Svetoslav Ganov42138042012-03-20 11:51:39 -070072 private static final String LOG_TAG = "TouchExplorer";
Svetoslav Ganov736c2752011-04-22 18:30:36 -070073
74 // States this explorer can be in.
75 private static final int STATE_TOUCH_EXPLORING = 0x00000001;
76 private static final int STATE_DRAGGING = 0x00000002;
77 private static final int STATE_DELEGATING = 0x00000004;
Svetoslav Ganov42138042012-03-20 11:51:39 -070078 private static final int STATE_GESTURE_DETECTING = 0x00000005;
Svetoslav Ganov736c2752011-04-22 18:30:36 -070079
Svetoslav Ganov736c2752011-04-22 18:30:36 -070080 // The minimum of the cosine between the vectors of two moving
81 // pointers so they can be considered moving in the same direction.
Svetoslav Ganov12a024c2011-09-03 19:52:36 -070082 private static final float MAX_DRAGGING_ANGLE_COS = 0.525321989f; // cos(pi/4)
Svetoslav Ganov736c2752011-04-22 18:30:36 -070083
Svetoslav Ganovf8044202011-08-26 20:33:33 -070084 // Constant referring to the ids bits of all pointers.
85 private static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF;
Svetoslav Ganov736c2752011-04-22 18:30:36 -070086
Svetoslav Ganov42138042012-03-20 11:51:39 -070087 // This constant captures the current implementation detail that
88 // pointer IDs are between 0 and 31 inclusive (subject to change).
89 // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
Svetoslav Ganove15ccb92012-05-16 15:48:55 -070090 private static final int MAX_POINTER_COUNT = 32;
Svetoslav Ganov42138042012-03-20 11:51:39 -070091
92 // Invalid pointer ID.
Svetoslav Ganove15ccb92012-05-16 15:48:55 -070093 private static final int INVALID_POINTER_ID = -1;
94
95 // The velocity above which we detect gestures.
96 private static final int GESTURE_DETECTION_VELOCITY_DIP = 1000;
97
98 // The minimal distance before we take the middle of the distance between
99 // the two dragging pointers as opposed to use the location of the primary one.
100 private static final int MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP = 200;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700101
Svetoslav Ganov95068e52012-06-13 21:01:51 -0700102 // The timeout after which we are no longer trying to detect a gesture.
103 private static final int EXIT_GESTURE_DETECTION_TIMEOUT = 2000;
104
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700105 // Temporary array for storing pointer IDs.
106 private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT];
107
Svetoslav Ganove47957a2012-06-05 14:46:50 -0700108 // Timeout before trying to decide what the user is trying to do.
109 private final int mDetermineUserIntentTimeout;
110
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700111 // Timeout within which we try to detect a tap.
112 private final int mTapTimeout;
113
114 // Timeout within which we try to detect a double tap.
115 private final int mDoubleTapTimeout;
116
117 // Slop between the down and up tap to be a tap.
118 private final int mTouchSlop;
119
120 // Slop between the first and second tap to be a double tap.
121 private final int mDoubleTapSlop;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700122
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700123 // The current state of the touch explorer.
124 private int mCurrentState = STATE_TOUCH_EXPLORING;
125
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700126 // The ID of the pointer used for dragging.
127 private int mDraggingPointerId;
128
129 // Handler for performing asynchronous operations.
130 private final Handler mHandler;
131
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700132 // Command for delayed sending of a hover enter event.
133 private final SendHoverDelayed mSendHoverEnterDelayed;
134
135 // Command for delayed sending of a hover exit event.
136 private final SendHoverDelayed mSendHoverExitDelayed;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700137
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700138 // Command for delayed sending of touch exploration end events.
139 private final SendAccessibilityEventDelayed mSendTouchExplorationEndDelayed;
140
141 // Command for delayed sending of touch interaction end events.
142 private final SendAccessibilityEventDelayed mSendTouchInteractionEndDelayed;
Svetoslav Ganovfe304b82012-09-28 11:12:34 -0700143
Svetoslav Ganovf5a07902011-07-24 19:20:17 -0700144 // Command for delayed sending of a long press.
145 private final PerformLongPressDelayed mPerformLongPressDelayed;
146
Svetoslav Ganov95068e52012-06-13 21:01:51 -0700147 // Command for exiting gesture detection mode after a timeout.
148 private final ExitGestureDetectionModeDelayed mExitGestureDetectionModeDelayed;
149
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700150 // Helper to detect and react to double tap in touch explore mode.
151 private final DoubleTapDetector mDoubleTapDetector;
152
153 // The scaled minimal distance before we take the middle of the distance between
154 // the two dragging pointers as opposed to use the location of the primary one.
155 private final int mScaledMinPointerDistanceToUseMiddleLocation;
156
157 // The scaled velocity above which we detect gestures.
158 private final int mScaledGestureDetectionVelocity;
159
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -0700160 // The handler to which to delegate events.
161 private EventStreamTransformation mNext;
162
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700163 // Helper to track gesture velocity.
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700164 private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700165
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700166 // Helper class to track received pointers.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700167 private final ReceivedPointerTracker mReceivedPointerTracker;
168
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700169 // Helper class to track injected pointers.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700170 private final InjectedPointerTracker mInjectedPointerTracker;
171
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700172 // Handle to the accessibility manager service.
173 private final AccessibilityManagerService mAms;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700174
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700175 // Temporary rectangle to avoid instantiation.
176 private final Rect mTempRect = new Rect();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700177
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700178 // Context in which this explorer operates.
179 private final Context mContext;
180
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700181 // The X of the previous event.
182 private float mPreviousX;
183
184 // The Y of the previous event.
185 private float mPreviousY;
186
187 // Buffer for storing points for gesture detection.
188 private final ArrayList<GesturePoint> mStrokeBuffer = new ArrayList<GesturePoint>(100);
189
190 // The minimal delta between moves to add a gesture point.
191 private static final int TOUCH_TOLERANCE = 3;
192
193 // The minimal score for accepting a predicted gesture.
194 private static final float MIN_PREDICTION_SCORE = 2.0f;
195
196 // The library for gesture detection.
197 private GestureLibrary mGestureLibrary;
198
199 // The long pressing pointer id if coordinate remapping is needed.
Svetoslav Ganovaeb8d0e2012-10-02 14:16:08 -0700200 private int mLongPressingPointerId = -1;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700201
202 // The long pressing pointer X if coordinate remapping is needed.
203 private int mLongPressingPointerDeltaX;
204
205 // The long pressing pointer Y if coordinate remapping is needed.
206 private int mLongPressingPointerDeltaY;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700207
Svetoslav Ganov385d9f22012-06-07 16:35:04 -0700208 // The id of the last touch explored window.
209 private int mLastTouchedWindowId;
210
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700211 // Whether touch exploration is in progress.
212 private boolean mTouchExplorationInProgress;
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700213
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700214 /**
215 * Creates a new instance.
216 *
217 * @param inputFilter The input filter associated with this explorer.
218 * @param context A context handle for accessing resources.
219 */
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -0700220 public TouchExplorer(Context context, AccessibilityManagerService service) {
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700221 mContext = context;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700222 mAms = service;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700223 mReceivedPointerTracker = new ReceivedPointerTracker(context);
224 mInjectedPointerTracker = new InjectedPointerTracker();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700225 mTapTimeout = ViewConfiguration.getTapTimeout();
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700226 mDetermineUserIntentTimeout = ViewConfiguration.getDoubleTapTimeout();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700227 mDoubleTapTimeout = ViewConfiguration.getDoubleTapTimeout();
228 mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
229 mDoubleTapSlop = ViewConfiguration.get(context).getScaledDoubleTapSlop();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700230 mHandler = new Handler(context.getMainLooper());
Svetoslav Ganovf5a07902011-07-24 19:20:17 -0700231 mPerformLongPressDelayed = new PerformLongPressDelayed();
Svetoslav Ganov95068e52012-06-13 21:01:51 -0700232 mExitGestureDetectionModeDelayed = new ExitGestureDetectionModeDelayed();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700233 mGestureLibrary = GestureLibraries.fromRawResource(context, R.raw.accessibility_gestures);
Casey Burkhardtea6fbc02012-06-19 16:24:10 -0700234 mGestureLibrary.setOrientationStyle(8);
235 mGestureLibrary.setSequenceType(GestureStore.SEQUENCE_SENSITIVE);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700236 mGestureLibrary.load();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700237 mSendHoverEnterDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_ENTER, true);
238 mSendHoverExitDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_EXIT, false);
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700239 mSendTouchExplorationEndDelayed = new SendAccessibilityEventDelayed(
240 AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END,
241 mDetermineUserIntentTimeout);
242 mSendTouchInteractionEndDelayed = new SendAccessibilityEventDelayed(
243 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END,
244 mDetermineUserIntentTimeout);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700245 mDoubleTapDetector = new DoubleTapDetector();
246 final float density = context.getResources().getDisplayMetrics().density;
247 mScaledMinPointerDistanceToUseMiddleLocation =
248 (int) (MIN_POINTER_DISTANCE_TO_USE_MIDDLE_LOCATION_DIP * density);
249 mScaledGestureDetectionVelocity = (int) (GESTURE_DETECTION_VELOCITY_DIP * density);
250 }
251
252 public void clear() {
253 // If we have not received an event then we are in initial
254 // state. Therefore, there is not need to clean anything.
255 MotionEvent event = mReceivedPointerTracker.getLastReceivedEvent();
256 if (event != null) {
257 clear(mReceivedPointerTracker.getLastReceivedEvent(), WindowManagerPolicy.FLAG_TRUSTED);
258 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700259 }
260
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -0700261 public void onDestroy() {
262 // TODO: Implement
263 }
264
265 private void clear(MotionEvent event, int policyFlags) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700266 switch (mCurrentState) {
267 case STATE_TOUCH_EXPLORING: {
268 // If a touch exploration gesture is in progress send events for its end.
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700269 sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700270 } break;
271 case STATE_DRAGGING: {
272 mDraggingPointerId = INVALID_POINTER_ID;
273 // Send exit to all pointers that we have delivered.
274 sendUpForInjectedDownPointers(event, policyFlags);
275 } break;
276 case STATE_DELEGATING: {
277 // Send exit to all pointers that we have delivered.
278 sendUpForInjectedDownPointers(event, policyFlags);
279 } break;
280 case STATE_GESTURE_DETECTING: {
281 // Clear the current stroke.
282 mStrokeBuffer.clear();
283 } break;
284 }
285 // Remove all pending callbacks.
286 mSendHoverEnterDelayed.remove();
287 mSendHoverExitDelayed.remove();
288 mPerformLongPressDelayed.remove();
Svetoslav Ganov95068e52012-06-13 21:01:51 -0700289 mExitGestureDetectionModeDelayed.remove();
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700290 mSendTouchExplorationEndDelayed.remove();
291 mSendTouchInteractionEndDelayed.remove();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700292 // Reset the pointer trackers.
293 mReceivedPointerTracker.clear();
294 mInjectedPointerTracker.clear();
295 // Clear the double tap detector
296 mDoubleTapDetector.clear();
297 // Go to initial state.
298 // Clear the long pressing pointer remap data.
299 mLongPressingPointerId = -1;
300 mLongPressingPointerDeltaX = 0;
301 mLongPressingPointerDeltaY = 0;
302 mCurrentState = STATE_TOUCH_EXPLORING;
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -0700303 if (mNext != null) {
304 mNext.clear();
305 }
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700306 mTouchExplorationInProgress = false;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700307 }
308
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -0700309 @Override
310 public void setNext(EventStreamTransformation next) {
311 mNext = next;
312 }
313
314 @Override
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700315 public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700316 if (DEBUG) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700317 Slog.d(LOG_TAG, "Received event: " + event + ", policyFlags=0x"
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700318 + Integer.toHexString(policyFlags));
Svetoslav Ganov42138042012-03-20 11:51:39 -0700319 Slog.d(LOG_TAG, getStateSymbolicName(mCurrentState));
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700320 }
321
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700322 mReceivedPointerTracker.onMotionEvent(rawEvent);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700323
324 switch(mCurrentState) {
325 case STATE_TOUCH_EXPLORING: {
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700326 handleMotionEventStateTouchExploring(event, rawEvent, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700327 } break;
328 case STATE_DRAGGING: {
329 handleMotionEventStateDragging(event, policyFlags);
330 } break;
331 case STATE_DELEGATING: {
332 handleMotionEventStateDelegating(event, policyFlags);
333 } break;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700334 case STATE_GESTURE_DETECTING: {
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700335 handleMotionEventGestureDetecting(rawEvent, policyFlags);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700336 } break;
337 default:
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700338 throw new IllegalStateException("Illegal state: " + mCurrentState);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700339 }
340 }
341
Svetoslav Ganov86783472012-06-06 21:12:20 -0700342 public void onAccessibilityEvent(AccessibilityEvent event) {
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700343 final int eventType = event.getEventType();
344
345 // The event for gesture end should be strictly after the
346 // last hover exit event.
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700347 if (mSendTouchExplorationEndDelayed.isPending()
Svetoslav Ganov8b681cb2012-09-14 15:20:45 -0700348 && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700349 mSendTouchExplorationEndDelayed.remove();
Svetoslav Ganov8b681cb2012-09-14 15:20:45 -0700350 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END);
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700351 }
352
353 // The event for touch interaction end should be strictly after the
354 // last hover exit and the touch exploration gesture end events.
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700355 if (mSendTouchInteractionEndDelayed.isPending()
Svetoslav Ganov8b681cb2012-09-14 15:20:45 -0700356 && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700357 mSendTouchInteractionEndDelayed.remove();
Svetoslav Ganov8b681cb2012-09-14 15:20:45 -0700358 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700359 }
360
Svetoslav Ganov86783472012-06-06 21:12:20 -0700361 // If a new window opens or the accessibility focus moves we no longer
362 // want to click/long press on the last touch explored location.
Svetoslav Ganov86783472012-06-06 21:12:20 -0700363 switch (eventType) {
364 case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
365 case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
Svetoslav Ganov5d043ce2012-06-14 10:30:00 -0700366 if (mInjectedPointerTracker.mLastInjectedHoverEventForClick != null) {
367 mInjectedPointerTracker.mLastInjectedHoverEventForClick.recycle();
368 mInjectedPointerTracker.mLastInjectedHoverEventForClick = null;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700369 }
Svetoslav Ganov385d9f22012-06-07 16:35:04 -0700370 mLastTouchedWindowId = -1;
371 } break;
372 case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
373 case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: {
374 mLastTouchedWindowId = event.getWindowId();
Svetoslav Ganov86783472012-06-06 21:12:20 -0700375 } break;
376 }
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -0700377 if (mNext != null) {
378 mNext.onAccessibilityEvent(event);
379 }
Svetoslav Ganov86783472012-06-06 21:12:20 -0700380 }
381
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700382 /**
383 * Handles a motion event in touch exploring state.
384 *
385 * @param event The event to be handled.
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700386 * @param rawEvent The raw (unmodified) motion event.
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700387 * @param policyFlags The policy flags associated with the event.
388 */
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700389 private void handleMotionEventStateTouchExploring(MotionEvent event, MotionEvent rawEvent,
390 int policyFlags) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700391 ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700392 final int activePointerCount = receivedTracker.getActivePointerCount();
393
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700394 mVelocityTracker.addMovement(rawEvent);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700395
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700396 mDoubleTapDetector.onMotionEvent(event, policyFlags);
397
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700398 switch (event.getActionMasked()) {
Svetoslav Ganovf5a07902011-07-24 19:20:17 -0700399 case MotionEvent.ACTION_DOWN:
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700400 // Pre-feed the motion events to the gesture detector since we
401 // have a distance slop before getting into gesture detection
402 // mode and not using the points within this slop significantly
403 // decreases the quality of gesture recognition.
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700404 handleMotionEventGestureDetecting(rawEvent, policyFlags);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700405 //$FALL-THROUGH$
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700406 case MotionEvent.ACTION_POINTER_DOWN: {
407 switch (activePointerCount) {
408 case 0: {
409 throw new IllegalStateException("The must always be one active pointer in"
410 + "touch exploring state!");
411 }
412 case 1: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700413 // If we still have not notified the user for the last
414 // touch, we figure out what to do. If were waiting
415 // we resent the delayed callback and wait again.
416 if (mSendHoverEnterDelayed.isPending()) {
417 mSendHoverEnterDelayed.remove();
418 mSendHoverExitDelayed.remove();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700419 }
420
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700421 if (mSendTouchExplorationEndDelayed.isPending()) {
422 mSendTouchExplorationEndDelayed.forceSendAndRemove();
423 }
424
425 if (mSendTouchInteractionEndDelayed.isPending()) {
426 mSendTouchInteractionEndDelayed.forceSendAndRemove();
427 }
428
429 // Every pointer that goes down is active until it moves or
430 // another one goes down. Hence, having more than one pointer
431 // down we have already send the interaction start event.
432 if (event.getPointerCount() == 1) {
433 sendAccessibilityEvent(
434 AccessibilityEvent.TYPE_TOUCH_INTERACTION_START);
Svetoslav Ganovfe304b82012-09-28 11:12:34 -0700435 }
436
Svetoslav Ganovebac1b72012-06-02 16:26:44 -0700437 mPerformLongPressDelayed.remove();
438
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700439 // If we have the first tap schedule a long press and break
440 // since we do not want to schedule hover enter because
441 // the delayed callback will kick in before the long click.
442 // This would lead to a state transition resulting in long
443 // pressing the item below the double taped area which is
444 // not necessary where accessibility focus is.
445 if (mDoubleTapDetector.firstTapDetected()) {
446 // We got a tap now post a long press action.
447 mPerformLongPressDelayed.post(event, policyFlags);
448 break;
449 }
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700450 if (!mTouchExplorationInProgress) {
451 // Deliver hover enter with a delay to have a chance
452 // to detect what the user is trying to do.
453 final int pointerId = receivedTracker.getPrimaryActivePointerId();
454 final int pointerIdBits = (1 << pointerId);
455 mSendHoverEnterDelayed.post(event, true, pointerIdBits, policyFlags);
456 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700457 } break;
458 default: {
459 /* do nothing - let the code for ACTION_MOVE decide what to do */
460 } break;
461 }
462 } break;
463 case MotionEvent.ACTION_MOVE: {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700464 final int pointerId = receivedTracker.getPrimaryActivePointerId();
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700465 final int pointerIndex = event.findPointerIndex(pointerId);
466 final int pointerIdBits = (1 << pointerId);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700467 switch (activePointerCount) {
468 case 0: {
469 /* do nothing - no active pointers so we swallow the event */
470 } break;
471 case 1: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700472 // We have not started sending events since we try to
473 // figure out what the user is doing.
474 if (mSendHoverEnterDelayed.isPending()) {
475 // Pre-feed the motion events to the gesture detector since we
476 // have a distance slop before getting into gesture detection
477 // mode and not using the points within this slop significantly
478 // decreases the quality of gesture recognition.
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700479 handleMotionEventGestureDetecting(rawEvent, policyFlags);
480 // It is *important* to use the distance traveled by the pointers
481 // on the screen which may or may not be magnified.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700482 final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700483 - rawEvent.getX(pointerIndex);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700484 final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700485 - rawEvent.getY(pointerIndex);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700486 final double moveDelta = Math.hypot(deltaX, deltaY);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700487 // The user has moved enough for us to decide.
488 if (moveDelta > mDoubleTapSlop) {
489 // Check whether the user is performing a gesture. We
490 // detect gestures if the pointer is moving above a
491 // given velocity.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700492 mVelocityTracker.computeCurrentVelocity(1000);
493 final float maxAbsVelocity = Math.max(
494 Math.abs(mVelocityTracker.getXVelocity(pointerId)),
495 Math.abs(mVelocityTracker.getYVelocity(pointerId)));
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700496 if (maxAbsVelocity > mScaledGestureDetectionVelocity) {
497 // We have to perform gesture detection, so
498 // clear the current state and try to detect.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700499 mCurrentState = STATE_GESTURE_DETECTING;
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700500 mVelocityTracker.clear();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700501 mSendHoverEnterDelayed.remove();
502 mSendHoverExitDelayed.remove();
503 mPerformLongPressDelayed.remove();
Svetoslav Ganov95068e52012-06-13 21:01:51 -0700504 mExitGestureDetectionModeDelayed.post();
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700505 // Send accessibility event to announce the start
506 // of gesture recognition.
507 sendAccessibilityEvent(
508 AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700509 } else {
510 // We have just decided that the user is touch,
511 // exploring so start sending events.
512 mSendHoverEnterDelayed.forceSendAndRemove();
513 mSendHoverExitDelayed.remove();
Svetoslav Ganovebac1b72012-06-02 16:26:44 -0700514 mPerformLongPressDelayed.remove();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700515 sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE,
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700516 pointerIdBits, policyFlags);
517 }
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700518 break;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700519 }
520 } else {
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700521 // Cancel the long press if pending and the user
522 // moved more than the slop.
523 if (mPerformLongPressDelayed.isPending()) {
524 final float deltaX =
525 receivedTracker.getReceivedPointerDownX(pointerId)
526 - rawEvent.getX(pointerIndex);
527 final float deltaY =
528 receivedTracker.getReceivedPointerDownY(pointerId)
529 - rawEvent.getY(pointerIndex);
530 final double moveDelta = Math.hypot(deltaX, deltaY);
531 // The user has moved enough for us to decide.
532 if (moveDelta > mTouchSlop) {
533 mPerformLongPressDelayed.remove();
534 }
535 }
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700536 // The user is wither double tapping or performing long
537 // press so do not send move events yet.
538 if (mDoubleTapDetector.firstTapDetected()) {
539 break;
540 }
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700541 sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700542 sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700543 policyFlags);
544 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700545 } break;
546 case 2: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700547 // More than one pointer so the user is not touch exploring
548 // and now we have to decide whether to delegate or drag.
549 if (mSendHoverEnterDelayed.isPending()) {
550 // We have not started sending events so cancel
551 // scheduled sending events.
552 mSendHoverEnterDelayed.remove();
553 mSendHoverExitDelayed.remove();
554 mPerformLongPressDelayed.remove();
555 } else {
Svetoslav Ganovebac1b72012-06-02 16:26:44 -0700556 mPerformLongPressDelayed.remove();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700557 // If the user is touch exploring the second pointer may be
558 // performing a double tap to activate an item without need
559 // for the user to lift his exploring finger.
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700560 // It is *important* to use the distance traveled by the pointers
561 // on the screen which may or may not be magnified.
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700562 final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700563 - rawEvent.getX(pointerIndex);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700564 final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700565 - rawEvent.getY(pointerIndex);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700566 final double moveDelta = Math.hypot(deltaX, deltaY);
567 if (moveDelta < mDoubleTapSlop) {
568 break;
569 }
570 // We are sending events so send exit and gesture
571 // end since we transition to another state.
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700572 sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700573 }
574
575 // We know that a new state transition is to happen and the
576 // new state will not be gesture recognition, so clear the
577 // stashed gesture strokes.
578 mStrokeBuffer.clear();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700579
580 if (isDraggingGesture(event)) {
581 // Two pointers moving in the same direction within
582 // a given distance perform a drag.
Svetoslav Ganov00f7b3f2011-06-07 19:24:10 -0700583 mCurrentState = STATE_DRAGGING;
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700584 mDraggingPointerId = pointerId;
585 sendMotionEvent(event, MotionEvent.ACTION_DOWN, pointerIdBits,
586 policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700587 } else {
588 // Two pointers moving arbitrary are delegated to the view hierarchy.
589 mCurrentState = STATE_DELEGATING;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700590 sendDownForAllActiveNotInjectedPointers(event, policyFlags);
591 }
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700592 mVelocityTracker.clear();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700593 } break;
594 default: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700595 // More than one pointer so the user is not touch exploring
596 // and now we have to decide whether to delegate or drag.
597 if (mSendHoverEnterDelayed.isPending()) {
598 // We have not started sending events so cancel
599 // scheduled sending events.
600 mSendHoverEnterDelayed.remove();
601 mSendHoverExitDelayed.remove();
602 mPerformLongPressDelayed.remove();
603 } else {
Svetoslav Ganovebac1b72012-06-02 16:26:44 -0700604 mPerformLongPressDelayed.remove();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700605 // We are sending events so send exit and gesture
606 // end since we transition to another state.
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700607 sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700608 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700609
610 // More than two pointers are delegated to the view hierarchy.
611 mCurrentState = STATE_DELEGATING;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700612 sendDownForAllActiveNotInjectedPointers(event, policyFlags);
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700613 mVelocityTracker.clear();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700614 }
615 }
616 } break;
617 case MotionEvent.ACTION_UP:
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700618 // We know that we do not need the pre-fed gesture points are not
619 // needed anymore since the last pointer just went up.
620 mStrokeBuffer.clear();
621 //$FALL-THROUGH$
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700622 case MotionEvent.ACTION_POINTER_UP: {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700623 final int pointerId = receivedTracker.getLastReceivedUpPointerId();
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700624 final int pointerIdBits = (1 << pointerId);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700625 switch (activePointerCount) {
626 case 0: {
627 // If the pointer that went up was not active we have nothing to do.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700628 if (!receivedTracker.wasLastReceivedUpPointerActive()) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700629 break;
630 }
631
Svetoslav Ganovf5a07902011-07-24 19:20:17 -0700632 mPerformLongPressDelayed.remove();
633
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700634 // If we have not delivered the enter schedule exit.
635 if (mSendHoverEnterDelayed.isPending()) {
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700636 mSendHoverExitDelayed.post(event, false, pointerIdBits, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700637 } else {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700638 // The user is touch exploring so we send events for end.
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700639 sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
640 }
641
642 if (!mSendTouchInteractionEndDelayed.isPending()) {
643 mSendTouchInteractionEndDelayed.post();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700644 }
645 } break;
646 }
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700647 mVelocityTracker.clear();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700648 } break;
649 case MotionEvent.ACTION_CANCEL: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700650 clear(event, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700651 } break;
652 }
653 }
654
655 /**
656 * Handles a motion event in dragging state.
657 *
658 * @param event The event to be handled.
659 * @param policyFlags The policy flags associated with the event.
660 */
661 private void handleMotionEventStateDragging(MotionEvent event, int policyFlags) {
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700662 final int pointerIdBits = (1 << mDraggingPointerId);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700663 switch (event.getActionMasked()) {
664 case MotionEvent.ACTION_DOWN: {
665 throw new IllegalStateException("Dragging state can be reached only if two "
666 + "pointers are already down");
667 }
668 case MotionEvent.ACTION_POINTER_DOWN: {
669 // We are in dragging state so we have two pointers and another one
670 // goes down => delegate the three pointers to the view hierarchy
671 mCurrentState = STATE_DELEGATING;
Svetoslav Ganovec33d562012-10-03 17:00:18 -0700672 if (mDraggingPointerId != INVALID_POINTER_ID) {
673 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
674 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700675 sendDownForAllActiveNotInjectedPointers(event, policyFlags);
676 } break;
677 case MotionEvent.ACTION_MOVE: {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700678 final int activePointerCount = mReceivedPointerTracker.getActivePointerCount();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700679 switch (activePointerCount) {
Svetoslav Ganov2e1c66b2011-10-11 18:19:10 -0700680 case 1: {
681 // do nothing
682 } break;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700683 case 2: {
684 if (isDraggingGesture(event)) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700685 // If the dragging pointer are closer that a given distance we
686 // use the location of the primary one. Otherwise, we take the
687 // middle between the pointers.
688 int[] pointerIds = mTempPointerIds;
689 mReceivedPointerTracker.populateActivePointerIds(pointerIds);
690
691 final int firstPtrIndex = event.findPointerIndex(pointerIds[0]);
692 final int secondPtrIndex = event.findPointerIndex(pointerIds[1]);
693
694 final float firstPtrX = event.getX(firstPtrIndex);
695 final float firstPtrY = event.getY(firstPtrIndex);
696 final float secondPtrX = event.getX(secondPtrIndex);
697 final float secondPtrY = event.getY(secondPtrIndex);
698
699 final float deltaX = firstPtrX - secondPtrX;
700 final float deltaY = firstPtrY - secondPtrY;
701 final double distance = Math.hypot(deltaX, deltaY);
702
703 if (distance > mScaledMinPointerDistanceToUseMiddleLocation) {
704 event.setLocation(deltaX / 2, deltaY / 2);
705 }
706
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700707 // If still dragging send a drag event.
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700708 sendMotionEvent(event, MotionEvent.ACTION_MOVE, pointerIdBits,
709 policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700710 } else {
711 // The two pointers are moving either in different directions or
712 // no close enough => delegate the gesture to the view hierarchy.
713 mCurrentState = STATE_DELEGATING;
714 // Send an event to the end of the drag gesture.
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700715 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
716 policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700717 // Deliver all active pointers to the view hierarchy.
718 sendDownForAllActiveNotInjectedPointers(event, policyFlags);
719 }
720 } break;
721 default: {
722 mCurrentState = STATE_DELEGATING;
723 // Send an event to the end of the drag gesture.
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700724 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
725 policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700726 // Deliver all active pointers to the view hierarchy.
727 sendDownForAllActiveNotInjectedPointers(event, policyFlags);
728 }
729 }
730 } break;
Svetoslav Ganovaeb8d0e2012-10-02 14:16:08 -0700731 case MotionEvent.ACTION_POINTER_UP: {
732 final int pointerId = event.getPointerId(event.getActionIndex());
733 if (pointerId == mDraggingPointerId) {
734 mDraggingPointerId = INVALID_POINTER_ID;
735 // Send an event to the end of the drag gesture.
736 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
737 }
738 } break;
Svetoslav Ganov2e1c66b2011-10-11 18:19:10 -0700739 case MotionEvent.ACTION_UP: {
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700740 // Announce the end of a new touch interaction.
741 sendAccessibilityEvent(
742 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
Svetoslav Ganovaeb8d0e2012-10-02 14:16:08 -0700743 final int pointerId = event.getPointerId(event.getActionIndex());
744 if (pointerId == mDraggingPointerId) {
745 mDraggingPointerId = INVALID_POINTER_ID;
746 // Send an event to the end of the drag gesture.
747 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
748 }
Svetoslav Ganov2e1c66b2011-10-11 18:19:10 -0700749 mCurrentState = STATE_TOUCH_EXPLORING;
750 } break;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700751 case MotionEvent.ACTION_CANCEL: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700752 clear(event, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700753 } break;
754 }
755 }
756
757 /**
758 * Handles a motion event in delegating state.
759 *
760 * @param event The event to be handled.
761 * @param policyFlags The policy flags associated with the event.
762 */
Svetoslav Ganov2e1c66b2011-10-11 18:19:10 -0700763 private void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700764 switch (event.getActionMasked()) {
765 case MotionEvent.ACTION_DOWN: {
766 throw new IllegalStateException("Delegating state can only be reached if "
767 + "there is at least one pointer down!");
768 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700769 case MotionEvent.ACTION_MOVE: {
770 // Check whether some other pointer became active because they have moved
771 // a given distance and if such exist send them to the view hierarchy
Svetoslav Ganov42138042012-03-20 11:51:39 -0700772 final int notInjectedCount = getNotInjectedActivePointerCount(
773 mReceivedPointerTracker, mInjectedPointerTracker);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700774 if (notInjectedCount > 0) {
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700775 MotionEvent prototype = MotionEvent.obtain(event);
776 sendDownForAllActiveNotInjectedPointers(prototype, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700777 }
778 } break;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700779 case MotionEvent.ACTION_UP:
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700780 // Announce the end of a new touch interaction.
781 sendAccessibilityEvent(
782 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
783 //$FALL-THROUGH$
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700784 case MotionEvent.ACTION_POINTER_UP: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700785 mLongPressingPointerId = -1;
786 mLongPressingPointerDeltaX = 0;
787 mLongPressingPointerDeltaY = 0;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700788 // No active pointers => go to initial state.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700789 if (mReceivedPointerTracker.getActivePointerCount() == 0) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700790 mCurrentState = STATE_TOUCH_EXPLORING;
791 }
792 } break;
793 case MotionEvent.ACTION_CANCEL: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700794 clear(event, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700795 } break;
796 }
797 // Deliver the event striping out inactive pointers.
798 sendMotionEventStripInactivePointers(event, policyFlags);
799 }
800
Svetoslav Ganov42138042012-03-20 11:51:39 -0700801 private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) {
802 switch (event.getActionMasked()) {
803 case MotionEvent.ACTION_DOWN: {
804 final float x = event.getX();
805 final float y = event.getY();
806 mPreviousX = x;
807 mPreviousY = y;
808 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
809 } break;
810 case MotionEvent.ACTION_MOVE: {
811 final float x = event.getX();
812 final float y = event.getY();
813 final float dX = Math.abs(x - mPreviousX);
814 final float dY = Math.abs(y - mPreviousY);
815 if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) {
816 mPreviousX = x;
817 mPreviousY = y;
818 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
819 }
820 } break;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700821 case MotionEvent.ACTION_UP: {
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700822 // Announce the end of gesture recognition.
823 sendAccessibilityEvent(
824 AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
825 // Announce the end of a new touch interaction.
826 sendAccessibilityEvent(
827 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
828
Svetoslav Ganov42138042012-03-20 11:51:39 -0700829 float x = event.getX();
830 float y = event.getY();
831 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
832
833 Gesture gesture = new Gesture();
834 gesture.addStroke(new GestureStroke(mStrokeBuffer));
835
836 ArrayList<Prediction> predictions = mGestureLibrary.recognize(gesture);
837 if (!predictions.isEmpty()) {
838 Prediction bestPrediction = predictions.get(0);
839 if (bestPrediction.score >= MIN_PREDICTION_SCORE) {
840 if (DEBUG) {
841 Slog.i(LOG_TAG, "gesture: " + bestPrediction.name + " score: "
842 + bestPrediction.score);
843 }
844 try {
845 final int gestureId = Integer.parseInt(bestPrediction.name);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700846 mAms.onGesture(gestureId);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700847 } catch (NumberFormatException nfe) {
848 Slog.w(LOG_TAG, "Non numeric gesture id:" + bestPrediction.name);
849 }
850 }
851 }
852
853 mStrokeBuffer.clear();
Svetoslav Ganov95068e52012-06-13 21:01:51 -0700854 mExitGestureDetectionModeDelayed.remove();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700855 mCurrentState = STATE_TOUCH_EXPLORING;
856 } break;
857 case MotionEvent.ACTION_CANCEL: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700858 clear(event, policyFlags);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700859 } break;
860 }
861 }
862
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700863 /**
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700864 * Sends an accessibility event of the given type.
865 *
866 * @param type The event type.
867 */
868 private void sendAccessibilityEvent(int type) {
869 AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
870 if (accessibilityManager.isEnabled()) {
871 AccessibilityEvent event = AccessibilityEvent.obtain(type);
872 accessibilityManager.sendAccessibilityEvent(event);
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700873 switch (type) {
874 case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: {
875 mTouchExplorationInProgress = true;
876 } break;
877 case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: {
878 mTouchExplorationInProgress = false;
879 } break;
880 }
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700881 }
882 }
883
884 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700885 * Sends down events to the view hierarchy for all active pointers which are
886 * not already being delivered i.e. pointers that are not yet injected.
887 *
888 * @param prototype The prototype from which to create the injected events.
889 * @param policyFlags The policy flags associated with the event.
890 */
891 private void sendDownForAllActiveNotInjectedPointers(MotionEvent prototype, int policyFlags) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700892 ReceivedPointerTracker receivedPointers = mReceivedPointerTracker;
893 InjectedPointerTracker injectedPointers = mInjectedPointerTracker;
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700894 int pointerIdBits = 0;
895 final int pointerCount = prototype.getPointerCount();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700896
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700897 // Find which pointers are already injected.
898 for (int i = 0; i < pointerCount; i++) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700899 final int pointerId = prototype.getPointerId(i);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700900 if (injectedPointers.isInjectedPointerDown(pointerId)) {
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700901 pointerIdBits |= (1 << pointerId);
902 }
903 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700904
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700905 // Inject the active and not injected pointers.
906 for (int i = 0; i < pointerCount; i++) {
907 final int pointerId = prototype.getPointerId(i);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700908 // Skip inactive pointers.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700909 if (!receivedPointers.isActivePointer(pointerId)) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700910 continue;
911 }
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700912 // Do not send event for already delivered pointers.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700913 if (injectedPointers.isInjectedPointerDown(pointerId)) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700914 continue;
915 }
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700916 pointerIdBits |= (1 << pointerId);
917 final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
918 sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
919 }
920 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700921
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700922 /**
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700923 * Sends the exit events if needed. Such events are hover exit and touch explore
924 * gesture end.
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700925 *
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700926 * @param policyFlags The policy flags associated with the event.
927 */
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700928 private void sendHoverExitAndTouchExplorationGestureEndIfNeeded(int policyFlags) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700929 MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
930 if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
931 final int pointerIdBits = event.getPointerIdBits();
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700932 if (!mSendTouchExplorationEndDelayed.isPending()) {
933 mSendTouchExplorationEndDelayed.post();
Svetoslav Ganovfe304b82012-09-28 11:12:34 -0700934 }
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700935 sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags);
936 }
937 }
938
939 /**
940 * Sends the enter events if needed. Such events are hover enter and touch explore
941 * gesture start.
942 *
943 * @param policyFlags The policy flags associated with the event.
944 */
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700945 private void sendTouchExplorationGestureStartAndHoverEnterIfNeeded(int policyFlags) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700946 MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
947 if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
948 final int pointerIdBits = event.getPointerIdBits();
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700949 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700950 sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700951 }
952 }
953
954 /**
955 * Sends up events to the view hierarchy for all active pointers which are
956 * already being delivered i.e. pointers that are injected.
957 *
958 * @param prototype The prototype from which to create the injected events.
959 * @param policyFlags The policy flags associated with the event.
960 */
961 private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700962 final InjectedPointerTracker injectedTracked = mInjectedPointerTracker;
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700963 int pointerIdBits = 0;
964 final int pointerCount = prototype.getPointerCount();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700965 for (int i = 0; i < pointerCount; i++) {
966 final int pointerId = prototype.getPointerId(i);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700967 // Skip non injected down pointers.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700968 if (!injectedTracked.isInjectedPointerDown(pointerId)) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700969 continue;
970 }
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700971 pointerIdBits |= (1 << pointerId);
972 final int action = computeInjectionAction(MotionEvent.ACTION_UP, i);
973 sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700974 }
975 }
976
977 /**
978 * Sends a motion event by first stripping the inactive pointers.
979 *
980 * @param prototype The prototype from which to create the injected event.
981 * @param policyFlags The policy flags associated with the event.
982 */
983 private void sendMotionEventStripInactivePointers(MotionEvent prototype, int policyFlags) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700984 ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700985
986 // All pointers active therefore we just inject the event as is.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700987 if (prototype.getPointerCount() == receivedTracker.getActivePointerCount()) {
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700988 sendMotionEvent(prototype, prototype.getAction(), ALL_POINTER_ID_BITS, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700989 return;
990 }
991
992 // No active pointers and the one that just went up was not
993 // active, therefore we have nothing to do.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700994 if (receivedTracker.getActivePointerCount() == 0
995 && !receivedTracker.wasLastReceivedUpPointerActive()) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700996 return;
997 }
998
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700999 // If the action pointer going up/down is not active we have nothing to do.
1000 // However, for moves we keep going to report moves of active pointers.
1001 final int actionMasked = prototype.getActionMasked();
1002 final int actionPointerId = prototype.getPointerId(prototype.getActionIndex());
1003 if (actionMasked != MotionEvent.ACTION_MOVE) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001004 if (!receivedTracker.isActiveOrWasLastActiveUpPointer(actionPointerId)) {
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001005 return;
1006 }
1007 }
1008
1009 // If the pointer is active or the pointer that just went up
1010 // was active we keep the pointer data in the event.
Svetoslav Ganov91feae32011-05-19 18:16:31 -07001011 int pointerIdBits = 0;
1012 final int pointerCount = prototype.getPointerCount();
1013 for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) {
1014 final int pointerId = prototype.getPointerId(pointerIndex);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001015 if (receivedTracker.isActiveOrWasLastActiveUpPointer(pointerId)) {
Svetoslav Ganov91feae32011-05-19 18:16:31 -07001016 pointerIdBits |= (1 << pointerId);
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001017 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001018 }
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001019 sendMotionEvent(prototype, prototype.getAction(), pointerIdBits, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001020 }
1021
1022 /**
1023 * Sends an up and down events.
1024 *
1025 * @param prototype The prototype from which to create the injected events.
1026 * @param policyFlags The policy flags associated with the event.
1027 */
1028 private void sendActionDownAndUp(MotionEvent prototype, int policyFlags) {
Svetoslav Ganovbd206d12011-09-15 17:33:07 -07001029 // Tap with the pointer that last explored - we may have inactive pointers.
1030 final int pointerId = prototype.getPointerId(prototype.getActionIndex());
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001031 final int pointerIdBits = (1 << pointerId);
1032 sendMotionEvent(prototype, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags);
1033 sendMotionEvent(prototype, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001034 }
1035
1036 /**
Svetoslav Ganov91feae32011-05-19 18:16:31 -07001037 * Sends an event.
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001038 *
Svetoslav Ganov00f7b3f2011-06-07 19:24:10 -07001039 * @param prototype The prototype from which to create the injected events.
Svetoslav Ganov91feae32011-05-19 18:16:31 -07001040 * @param action The action of the event.
1041 * @param pointerIdBits The bits of the pointers to send.
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001042 * @param policyFlags The policy flags associated with the event.
1043 */
Svetoslav Ganov91feae32011-05-19 18:16:31 -07001044 private void sendMotionEvent(MotionEvent prototype, int action, int pointerIdBits,
1045 int policyFlags) {
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001046 prototype.setAction(action);
1047
1048 MotionEvent event = null;
1049 if (pointerIdBits == ALL_POINTER_ID_BITS) {
1050 event = prototype;
1051 } else {
1052 event = prototype.split(pointerIdBits);
1053 }
1054 if (action == MotionEvent.ACTION_DOWN) {
1055 event.setDownTime(event.getEventTime());
1056 } else {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001057 event.setDownTime(mInjectedPointerTracker.getLastInjectedDownEventTime());
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001058 }
1059
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001060 // If the user is long pressing but the long pressing pointer
1061 // was not exactly over the accessibility focused item we need
1062 // to remap the location of that pointer so the user does not
1063 // have to explicitly touch explore something to be able to
1064 // long press it, or even worse to avoid the user long pressing
1065 // on the wrong item since click and long press behave differently.
1066 if (mLongPressingPointerId >= 0) {
1067 final int remappedIndex = event.findPointerIndex(mLongPressingPointerId);
1068 final int pointerCount = event.getPointerCount();
1069 PointerProperties[] props = PointerProperties.createArray(pointerCount);
1070 PointerCoords[] coords = PointerCoords.createArray(pointerCount);
1071 for (int i = 0; i < pointerCount; i++) {
1072 event.getPointerProperties(i, props[i]);
1073 event.getPointerCoords(i, coords[i]);
1074 if (i == remappedIndex) {
1075 coords[i].x -= mLongPressingPointerDeltaX;
1076 coords[i].y -= mLongPressingPointerDeltaY;
1077 }
1078 }
1079 MotionEvent remapped = MotionEvent.obtain(event.getDownTime(),
1080 event.getEventTime(), event.getAction(), event.getPointerCount(),
1081 props, coords, event.getMetaState(), event.getButtonState(),
1082 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(),
1083 event.getSource(), event.getFlags());
1084 if (event != prototype) {
1085 event.recycle();
1086 }
1087 event = remapped;
1088 }
1089
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001090 if (DEBUG) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001091 Slog.d(LOG_TAG, "Injecting event: " + event + ", policyFlags=0x"
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001092 + Integer.toHexString(policyFlags));
1093 }
1094
1095 // Make sure that the user will see the event.
1096 policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -07001097 if (mNext != null) {
Svetoslav Ganov45af84a2012-10-01 16:25:08 -07001098 // TODO: For now pass null for the raw event since the touch
1099 // explorer is the last event transformation and it does
1100 // not care about the raw event.
1101 mNext.onMotionEvent(event, null, policyFlags);
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -07001102 }
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001103
Svetoslav Ganov42138042012-03-20 11:51:39 -07001104 mInjectedPointerTracker.onMotionEvent(event);
1105
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001106 if (event != prototype) {
1107 event.recycle();
1108 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001109 }
1110
1111 /**
1112 * Computes the action for an injected event based on a masked action
1113 * and a pointer index.
1114 *
1115 * @param actionMasked The masked action.
1116 * @param pointerIndex The index of the pointer which has changed.
1117 * @return The action to be used for injection.
1118 */
1119 private int computeInjectionAction(int actionMasked, int pointerIndex) {
1120 switch (actionMasked) {
1121 case MotionEvent.ACTION_DOWN:
1122 case MotionEvent.ACTION_POINTER_DOWN: {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001123 InjectedPointerTracker injectedTracker = mInjectedPointerTracker;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001124 // Compute the action based on how many down pointers are injected.
Svetoslav Ganov42138042012-03-20 11:51:39 -07001125 if (injectedTracker.getInjectedPointerDownCount() == 0) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001126 return MotionEvent.ACTION_DOWN;
1127 } else {
1128 return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
1129 | MotionEvent.ACTION_POINTER_DOWN;
1130 }
1131 }
1132 case MotionEvent.ACTION_POINTER_UP: {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001133 InjectedPointerTracker injectedTracker = mInjectedPointerTracker;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001134 // Compute the action based on how many down pointers are injected.
Svetoslav Ganov42138042012-03-20 11:51:39 -07001135 if (injectedTracker.getInjectedPointerDownCount() == 1) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001136 return MotionEvent.ACTION_UP;
1137 } else {
1138 return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
1139 | MotionEvent.ACTION_POINTER_UP;
1140 }
1141 }
1142 default:
1143 return actionMasked;
1144 }
1145 }
1146
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001147 private class DoubleTapDetector {
1148 private MotionEvent mDownEvent;
1149 private MotionEvent mFirstTapEvent;
1150
1151 public void onMotionEvent(MotionEvent event, int policyFlags) {
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -07001152 final int actionIndex = event.getActionIndex();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001153 final int action = event.getActionMasked();
1154 switch (action) {
1155 case MotionEvent.ACTION_DOWN:
1156 case MotionEvent.ACTION_POINTER_DOWN: {
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -07001157 if (mFirstTapEvent != null
1158 && !GestureUtils.isSamePointerContext(mFirstTapEvent, event)) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001159 clear();
1160 }
1161 mDownEvent = MotionEvent.obtain(event);
1162 } break;
1163 case MotionEvent.ACTION_UP:
1164 case MotionEvent.ACTION_POINTER_UP: {
1165 if (mDownEvent == null) {
1166 return;
1167 }
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -07001168 if (!GestureUtils.isSamePointerContext(mDownEvent, event)) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001169 clear();
1170 return;
1171 }
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -07001172 if (GestureUtils.isTap(mDownEvent, event, mTapTimeout, mTouchSlop,
1173 actionIndex)) {
1174 if (mFirstTapEvent == null || GestureUtils.isTimedOut(mFirstTapEvent,
1175 event, mDoubleTapTimeout)) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001176 mFirstTapEvent = MotionEvent.obtain(event);
1177 mDownEvent.recycle();
1178 mDownEvent = null;
1179 return;
1180 }
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -07001181 if (GestureUtils.isMultiTap(mFirstTapEvent, event, mDoubleTapTimeout,
1182 mDoubleTapSlop, actionIndex)) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001183 onDoubleTap(event, policyFlags);
1184 mFirstTapEvent.recycle();
1185 mFirstTapEvent = null;
1186 mDownEvent.recycle();
1187 mDownEvent = null;
1188 return;
1189 }
1190 mFirstTapEvent.recycle();
1191 mFirstTapEvent = null;
1192 } else {
1193 if (mFirstTapEvent != null) {
1194 mFirstTapEvent.recycle();
1195 mFirstTapEvent = null;
1196 }
1197 }
1198 mDownEvent.recycle();
1199 mDownEvent = null;
1200 } break;
1201 }
1202 }
1203
1204 public void onDoubleTap(MotionEvent secondTapUp, int policyFlags) {
1205 // This should never be called when more than two pointers are down.
1206 if (secondTapUp.getPointerCount() > 2) {
1207 return;
1208 }
1209
1210 // Remove pending event deliveries.
Svetoslav Ganov58d37b52012-09-18 12:04:19 -07001211 mSendHoverEnterDelayed.remove();
1212 mSendHoverExitDelayed.remove();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001213 mPerformLongPressDelayed.remove();
1214
Svetoslav Ganovf068fed2012-10-03 22:25:07 -07001215 if (mSendTouchExplorationEndDelayed.isPending()) {
1216 mSendTouchExplorationEndDelayed.forceSendAndRemove();
1217 }
1218 if (mSendTouchInteractionEndDelayed.isPending()) {
1219 mSendTouchInteractionEndDelayed.forceSendAndRemove();
1220 }
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001221
Svetoslav Ganov86783472012-06-06 21:12:20 -07001222 int clickLocationX;
1223 int clickLocationY;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001224
1225 final int pointerId = secondTapUp.getPointerId(secondTapUp.getActionIndex());
1226 final int pointerIndex = secondTapUp.findPointerIndex(pointerId);
Svetoslav Ganov86783472012-06-06 21:12:20 -07001227
Svetoslav Ganov5d043ce2012-06-14 10:30:00 -07001228 MotionEvent lastExploreEvent =
1229 mInjectedPointerTracker.getLastInjectedHoverEventForClick();
Svetoslav Ganov86783472012-06-06 21:12:20 -07001230 if (lastExploreEvent == null) {
1231 // No last touch explored event but there is accessibility focus in
1232 // the active window. We click in the middle of the focus bounds.
1233 Rect focusBounds = mTempRect;
1234 if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) {
1235 clickLocationX = focusBounds.centerX();
1236 clickLocationY = focusBounds.centerY();
1237 } else {
1238 // Out of luck - do nothing.
1239 return;
1240 }
1241 } else {
1242 // If the click is within the active window but not within the
1243 // accessibility focus bounds we click in the focus center.
1244 final int lastExplorePointerIndex = lastExploreEvent.getActionIndex();
1245 clickLocationX = (int) lastExploreEvent.getX(lastExplorePointerIndex);
1246 clickLocationY = (int) lastExploreEvent.getY(lastExplorePointerIndex);
1247 Rect activeWindowBounds = mTempRect;
Svetoslav Ganov385d9f22012-06-07 16:35:04 -07001248 if (mLastTouchedWindowId == mAms.getActiveWindowId()) {
1249 mAms.getActiveWindowBounds(activeWindowBounds);
1250 if (activeWindowBounds.contains(clickLocationX, clickLocationY)) {
1251 Rect focusBounds = mTempRect;
1252 if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) {
1253 if (!focusBounds.contains(clickLocationX, clickLocationY)) {
1254 clickLocationX = focusBounds.centerX();
1255 clickLocationY = focusBounds.centerY();
1256 }
Svetoslav Ganov86783472012-06-06 21:12:20 -07001257 }
1258 }
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001259 }
1260 }
1261
1262 // Do the click.
1263 PointerProperties[] properties = new PointerProperties[1];
1264 properties[0] = new PointerProperties();
1265 secondTapUp.getPointerProperties(pointerIndex, properties[0]);
1266 PointerCoords[] coords = new PointerCoords[1];
1267 coords[0] = new PointerCoords();
Svetoslav Ganov86783472012-06-06 21:12:20 -07001268 coords[0].x = clickLocationX;
1269 coords[0].y = clickLocationY;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001270 MotionEvent event = MotionEvent.obtain(secondTapUp.getDownTime(),
1271 secondTapUp.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties,
1272 coords, 0, 0, 1.0f, 1.0f, secondTapUp.getDeviceId(), 0,
1273 secondTapUp.getSource(), secondTapUp.getFlags());
1274 sendActionDownAndUp(event, policyFlags);
1275 event.recycle();
1276 }
1277
1278 public void clear() {
1279 if (mDownEvent != null) {
1280 mDownEvent.recycle();
1281 mDownEvent = null;
1282 }
1283 if (mFirstTapEvent != null) {
1284 mFirstTapEvent.recycle();
1285 mFirstTapEvent = null;
1286 }
1287 }
1288
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001289 public boolean firstTapDetected() {
1290 return mFirstTapEvent != null
1291 && SystemClock.uptimeMillis() - mFirstTapEvent.getEventTime() < mDoubleTapTimeout;
1292 }
1293 }
1294
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001295 /**
1296 * Determines whether a two pointer gesture is a dragging one.
1297 *
1298 * @param event The event with the pointer data.
1299 * @return True if the gesture is a dragging one.
1300 */
1301 private boolean isDraggingGesture(MotionEvent event) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001302 ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001303 int[] pointerIds = mTempPointerIds;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001304 receivedTracker.populateActivePointerIds(pointerIds);
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001305
1306 final int firstPtrIndex = event.findPointerIndex(pointerIds[0]);
1307 final int secondPtrIndex = event.findPointerIndex(pointerIds[1]);
1308
1309 final float firstPtrX = event.getX(firstPtrIndex);
1310 final float firstPtrY = event.getY(firstPtrIndex);
1311 final float secondPtrX = event.getX(secondPtrIndex);
1312 final float secondPtrY = event.getY(secondPtrIndex);
1313
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -07001314 final float firstPtrDownX = receivedTracker.getReceivedPointerDownX(firstPtrIndex);
1315 final float firstPtrDownY = receivedTracker.getReceivedPointerDownY(firstPtrIndex);
1316 final float secondPtrDownX = receivedTracker.getReceivedPointerDownX(secondPtrIndex);
1317 final float secondPtrDownY = receivedTracker.getReceivedPointerDownY(secondPtrIndex);
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001318
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -07001319 return GestureUtils.isDraggingGesture(firstPtrDownX, firstPtrDownY, secondPtrDownX,
1320 secondPtrDownY, firstPtrX, firstPtrY, secondPtrX, secondPtrY,
1321 MAX_DRAGGING_ANGLE_COS);
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001322 }
1323
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001324 /**
Svetoslav Ganov51cccf02011-06-27 12:00:54 -07001325 * Gets the symbolic name of a state.
1326 *
1327 * @param state A state.
1328 * @return The state symbolic name.
1329 */
1330 private static String getStateSymbolicName(int state) {
1331 switch (state) {
1332 case STATE_TOUCH_EXPLORING:
1333 return "STATE_TOUCH_EXPLORING";
1334 case STATE_DRAGGING:
1335 return "STATE_DRAGGING";
1336 case STATE_DELEGATING:
1337 return "STATE_DELEGATING";
Svetoslav Ganov42138042012-03-20 11:51:39 -07001338 case STATE_GESTURE_DETECTING:
1339 return "STATE_GESTURE_DETECTING";
Svetoslav Ganov51cccf02011-06-27 12:00:54 -07001340 default:
1341 throw new IllegalArgumentException("Unknown state: " + state);
1342 }
1343 }
1344
1345 /**
Svetoslav Ganov42138042012-03-20 11:51:39 -07001346 * @return The number of non injected active pointers.
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001347 */
Svetoslav Ganov42138042012-03-20 11:51:39 -07001348 private int getNotInjectedActivePointerCount(ReceivedPointerTracker receivedTracker,
1349 InjectedPointerTracker injectedTracker) {
1350 final int pointerState = receivedTracker.getActivePointers()
1351 & ~injectedTracker.getInjectedPointersDown();
1352 return Integer.bitCount(pointerState);
1353 }
1354
1355 /**
Svetoslav Ganov95068e52012-06-13 21:01:51 -07001356 * Class for delayed exiting from gesture detecting mode.
1357 */
1358 private final class ExitGestureDetectionModeDelayed implements Runnable {
1359
1360 public void post() {
1361 mHandler.postDelayed(this, EXIT_GESTURE_DETECTION_TIMEOUT);
1362 }
1363
1364 public void remove() {
1365 mHandler.removeCallbacks(this);
1366 }
1367
1368 @Override
1369 public void run() {
Svetoslav Ganovaed4b6f2012-09-28 10:06:18 -07001370 // Announce the end of gesture recognition.
1371 sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
1372 // Clearing puts is in touch exploration state with a finger already
1373 // down, so announce the transition to exploration state.
1374 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
Svetoslav Ganov95068e52012-06-13 21:01:51 -07001375 clear();
1376 }
1377 }
1378
1379 /**
Svetoslav Ganov42138042012-03-20 11:51:39 -07001380 * Class for delayed sending of long press.
1381 */
1382 private final class PerformLongPressDelayed implements Runnable {
1383 private MotionEvent mEvent;
1384 private int mPolicyFlags;
1385
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001386 public void post(MotionEvent prototype, int policyFlags) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001387 mEvent = MotionEvent.obtain(prototype);
1388 mPolicyFlags = policyFlags;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001389 mHandler.postDelayed(this, ViewConfiguration.getLongPressTimeout());
Svetoslav Ganov42138042012-03-20 11:51:39 -07001390 }
1391
1392 public void remove() {
Svetoslav Ganov77276b62012-09-14 10:23:00 -07001393 if (isPending()) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001394 mHandler.removeCallbacks(this);
1395 clear();
1396 }
1397 }
1398
Svetoslav Ganov77276b62012-09-14 10:23:00 -07001399 public boolean isPending() {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001400 return (mEvent != null);
1401 }
1402
1403 @Override
1404 public void run() {
Svetoslav Ganovebac1b72012-06-02 16:26:44 -07001405 // Active pointers should not be zero when running this command.
1406 if (mReceivedPointerTracker.getActivePointerCount() == 0) {
1407 return;
1408 }
1409
Svetoslav Ganov86783472012-06-06 21:12:20 -07001410 int clickLocationX;
1411 int clickLocationY;
Svetoslav Ganov238099c2012-06-01 13:52:54 -07001412
1413 final int pointerId = mEvent.getPointerId(mEvent.getActionIndex());
1414 final int pointerIndex = mEvent.findPointerIndex(pointerId);
Svetoslav Ganov86783472012-06-06 21:12:20 -07001415
Svetoslav Ganov5d043ce2012-06-14 10:30:00 -07001416 MotionEvent lastExploreEvent =
1417 mInjectedPointerTracker.getLastInjectedHoverEventForClick();
Svetoslav Ganov86783472012-06-06 21:12:20 -07001418 if (lastExploreEvent == null) {
1419 // No last touch explored event but there is accessibility focus in
1420 // the active window. We click in the middle of the focus bounds.
1421 Rect focusBounds = mTempRect;
1422 if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) {
1423 clickLocationX = focusBounds.centerX();
1424 clickLocationY = focusBounds.centerY();
1425 } else {
1426 // Out of luck - do nothing.
1427 return;
1428 }
1429 } else {
1430 // If the click is within the active window but not within the
1431 // accessibility focus bounds we click in the focus center.
1432 final int lastExplorePointerIndex = lastExploreEvent.getActionIndex();
1433 clickLocationX = (int) lastExploreEvent.getX(lastExplorePointerIndex);
1434 clickLocationY = (int) lastExploreEvent.getY(lastExplorePointerIndex);
1435 Rect activeWindowBounds = mTempRect;
Svetoslav Ganov385d9f22012-06-07 16:35:04 -07001436 if (mLastTouchedWindowId == mAms.getActiveWindowId()) {
1437 mAms.getActiveWindowBounds(activeWindowBounds);
1438 if (activeWindowBounds.contains(clickLocationX, clickLocationY)) {
1439 Rect focusBounds = mTempRect;
1440 if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) {
1441 if (!focusBounds.contains(clickLocationX, clickLocationY)) {
1442 clickLocationX = focusBounds.centerX();
1443 clickLocationY = focusBounds.centerY();
1444 }
Svetoslav Ganov86783472012-06-06 21:12:20 -07001445 }
1446 }
Svetoslav Ganov238099c2012-06-01 13:52:54 -07001447 }
1448 }
1449
Svetoslav Ganov86783472012-06-06 21:12:20 -07001450 mLongPressingPointerId = pointerId;
1451 mLongPressingPointerDeltaX = (int) mEvent.getX(pointerIndex) - clickLocationX;
1452 mLongPressingPointerDeltaY = (int) mEvent.getY(pointerIndex) - clickLocationY;
Svetoslav Ganov238099c2012-06-01 13:52:54 -07001453
Svetoslav Ganovf068fed2012-10-03 22:25:07 -07001454 sendHoverExitAndTouchExplorationGestureEndIfNeeded(mPolicyFlags);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001455
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001456 mCurrentState = STATE_DELEGATING;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001457 sendDownForAllActiveNotInjectedPointers(mEvent, mPolicyFlags);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001458 clear();
1459 }
1460
1461 private void clear() {
Svetoslav Ganov77276b62012-09-14 10:23:00 -07001462 if (!isPending()) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001463 return;
1464 }
1465 mEvent.recycle();
1466 mEvent = null;
1467 mPolicyFlags = 0;
1468 }
1469 }
1470
1471 /**
1472 * Class for delayed sending of hover events.
1473 */
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001474 class SendHoverDelayed implements Runnable {
1475 private final String LOG_TAG_SEND_HOVER_DELAYED = SendHoverDelayed.class.getName();
1476
1477 private final int mHoverAction;
1478 private final boolean mGestureStarted;
1479
1480 private MotionEvent mPrototype;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001481 private int mPointerIdBits;
1482 private int mPolicyFlags;
1483
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001484 public SendHoverDelayed(int hoverAction, boolean gestureStarted) {
1485 mHoverAction = hoverAction;
1486 mGestureStarted = gestureStarted;
1487 }
1488
Svetoslav Ganov77276b62012-09-14 10:23:00 -07001489 public void post(MotionEvent prototype, boolean touchExplorationInProgress,
1490 int pointerIdBits, int policyFlags) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001491 remove();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001492 mPrototype = MotionEvent.obtain(prototype);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001493 mPointerIdBits = pointerIdBits;
1494 mPolicyFlags = policyFlags;
Svetoslav Ganove47957a2012-06-05 14:46:50 -07001495 mHandler.postDelayed(this, mDetermineUserIntentTimeout);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001496 }
1497
1498 public float getX() {
1499 if (isPending()) {
1500 return mPrototype.getX();
1501 }
1502 return 0;
1503 }
1504
1505 public float getY() {
1506 if (isPending()) {
1507 return mPrototype.getY();
1508 }
1509 return 0;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001510 }
1511
1512 public void remove() {
1513 mHandler.removeCallbacks(this);
1514 clear();
1515 }
1516
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001517 private boolean isPending() {
1518 return (mPrototype != null);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001519 }
1520
1521 private void clear() {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001522 if (!isPending()) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001523 return;
1524 }
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001525 mPrototype.recycle();
1526 mPrototype = null;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001527 mPointerIdBits = -1;
1528 mPolicyFlags = 0;
1529 }
1530
1531 public void forceSendAndRemove() {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001532 if (isPending()) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001533 run();
1534 remove();
1535 }
1536 }
1537
1538 public void run() {
1539 if (DEBUG) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001540 Slog.d(LOG_TAG_SEND_HOVER_DELAYED, "Injecting motion event: "
1541 + MotionEvent.actionToString(mHoverAction));
1542 Slog.d(LOG_TAG_SEND_HOVER_DELAYED, mGestureStarted ?
1543 "touchExplorationGestureStarted" : "touchExplorationGestureEnded");
Svetoslav Ganov42138042012-03-20 11:51:39 -07001544 }
Svetoslav Ganovf068fed2012-10-03 22:25:07 -07001545 if (mGestureStarted) {
1546 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001547 } else {
Svetoslav Ganovf068fed2012-10-03 22:25:07 -07001548 if (!mSendTouchExplorationEndDelayed.isPending()) {
1549 mSendTouchExplorationEndDelayed.post();
1550 }
1551 if (mSendTouchInteractionEndDelayed.isPending()) {
1552 mSendTouchInteractionEndDelayed.remove();
1553 mSendTouchInteractionEndDelayed.post();
Svetoslav Ganov77276b62012-09-14 10:23:00 -07001554 }
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001555 }
1556 sendMotionEvent(mPrototype, mHoverAction, mPointerIdBits, mPolicyFlags);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001557 clear();
1558 }
1559 }
1560
Svetoslav Ganovf068fed2012-10-03 22:25:07 -07001561 private class SendAccessibilityEventDelayed implements Runnable {
1562 private final int mEventType;
1563 private final int mDelay;
1564
1565 public SendAccessibilityEventDelayed(int eventType, int delay) {
1566 mEventType = eventType;
1567 mDelay = delay;
1568 }
Svetoslav Ganovfe304b82012-09-28 11:12:34 -07001569
1570 public void remove() {
1571 mHandler.removeCallbacks(this);
1572 }
1573
1574 public void post() {
Svetoslav Ganovf068fed2012-10-03 22:25:07 -07001575 mHandler.postDelayed(this, mDelay);
Svetoslav Ganovfe304b82012-09-28 11:12:34 -07001576 }
1577
1578 public boolean isPending() {
1579 return mHandler.hasCallbacks(this);
1580 }
1581
1582 public void forceSendAndRemove() {
1583 if (isPending()) {
1584 run();
1585 remove();
1586 }
1587 }
1588
1589 @Override
1590 public void run() {
Svetoslav Ganovf068fed2012-10-03 22:25:07 -07001591 sendAccessibilityEvent(mEventType);
Svetoslav Ganovfe304b82012-09-28 11:12:34 -07001592 }
1593 }
1594
Svetoslav Ganov42138042012-03-20 11:51:39 -07001595 @Override
1596 public String toString() {
1597 return LOG_TAG;
1598 }
1599
1600 class InjectedPointerTracker {
1601 private static final String LOG_TAG_INJECTED_POINTER_TRACKER = "InjectedPointerTracker";
1602
1603 // Keep track of which pointers sent to the system are down.
1604 private int mInjectedPointersDown;
1605
1606 // The time of the last injected down.
1607 private long mLastInjectedDownEventTime;
1608
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001609 // The last injected hover event.
1610 private MotionEvent mLastInjectedHoverEvent;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001611
Svetoslav Ganov5d043ce2012-06-14 10:30:00 -07001612 // The last injected hover event used for performing clicks.
1613 private MotionEvent mLastInjectedHoverEventForClick;
1614
Svetoslav Ganov42138042012-03-20 11:51:39 -07001615 /**
1616 * Processes an injected {@link MotionEvent} event.
1617 *
1618 * @param event The event to process.
1619 */
1620 public void onMotionEvent(MotionEvent event) {
1621 final int action = event.getActionMasked();
1622 switch (action) {
1623 case MotionEvent.ACTION_DOWN:
1624 case MotionEvent.ACTION_POINTER_DOWN: {
1625 final int pointerId = event.getPointerId(event.getActionIndex());
1626 final int pointerFlag = (1 << pointerId);
1627 mInjectedPointersDown |= pointerFlag;
1628 mLastInjectedDownEventTime = event.getDownTime();
1629 } break;
1630 case MotionEvent.ACTION_UP:
1631 case MotionEvent.ACTION_POINTER_UP: {
1632 final int pointerId = event.getPointerId(event.getActionIndex());
1633 final int pointerFlag = (1 << pointerId);
1634 mInjectedPointersDown &= ~pointerFlag;
1635 if (mInjectedPointersDown == 0) {
1636 mLastInjectedDownEventTime = 0;
1637 }
1638 } break;
1639 case MotionEvent.ACTION_HOVER_ENTER:
1640 case MotionEvent.ACTION_HOVER_MOVE:
1641 case MotionEvent.ACTION_HOVER_EXIT: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001642 if (mLastInjectedHoverEvent != null) {
1643 mLastInjectedHoverEvent.recycle();
1644 }
1645 mLastInjectedHoverEvent = MotionEvent.obtain(event);
Svetoslav Ganov5d043ce2012-06-14 10:30:00 -07001646 if (mLastInjectedHoverEventForClick != null) {
1647 mLastInjectedHoverEventForClick.recycle();
1648 }
1649 mLastInjectedHoverEventForClick = MotionEvent.obtain(event);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001650 } break;
1651 }
1652 if (DEBUG) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001653 Slog.i(LOG_TAG_INJECTED_POINTER_TRACKER, "Injected pointer:\n" + toString());
Svetoslav Ganov42138042012-03-20 11:51:39 -07001654 }
1655 }
1656
1657 /**
1658 * Clears the internals state.
1659 */
1660 public void clear() {
1661 mInjectedPointersDown = 0;
1662 }
1663
1664 /**
1665 * @return The time of the last injected down event.
1666 */
1667 public long getLastInjectedDownEventTime() {
1668 return mLastInjectedDownEventTime;
1669 }
1670
1671 /**
1672 * @return The number of down pointers injected to the view hierarchy.
1673 */
1674 public int getInjectedPointerDownCount() {
1675 return Integer.bitCount(mInjectedPointersDown);
1676 }
1677
1678 /**
1679 * @return The bits of the injected pointers that are down.
1680 */
1681 public int getInjectedPointersDown() {
1682 return mInjectedPointersDown;
1683 }
1684
1685 /**
1686 * Whether an injected pointer is down.
1687 *
1688 * @param pointerId The unique pointer id.
1689 * @return True if the pointer is down.
1690 */
1691 public boolean isInjectedPointerDown(int pointerId) {
1692 final int pointerFlag = (1 << pointerId);
1693 return (mInjectedPointersDown & pointerFlag) != 0;
1694 }
1695
1696 /**
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001697 * @return The the last injected hover event.
Svetoslav Ganov42138042012-03-20 11:51:39 -07001698 */
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001699 public MotionEvent getLastInjectedHoverEvent() {
1700 return mLastInjectedHoverEvent;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001701 }
1702
Svetoslav Ganov5d043ce2012-06-14 10:30:00 -07001703 /**
1704 * @return The the last injected hover event.
1705 */
1706 public MotionEvent getLastInjectedHoverEventForClick() {
1707 return mLastInjectedHoverEventForClick;
1708 }
1709
Svetoslav Ganov42138042012-03-20 11:51:39 -07001710 @Override
1711 public String toString() {
1712 StringBuilder builder = new StringBuilder();
1713 builder.append("=========================");
1714 builder.append("\nDown pointers #");
1715 builder.append(Integer.bitCount(mInjectedPointersDown));
1716 builder.append(" [ ");
1717 for (int i = 0; i < MAX_POINTER_COUNT; i++) {
1718 if ((mInjectedPointersDown & i) != 0) {
1719 builder.append(i);
1720 builder.append(" ");
1721 }
1722 }
1723 builder.append("]");
1724 builder.append("\n=========================");
1725 return builder.toString();
1726 }
1727 }
1728
1729 class ReceivedPointerTracker {
1730 private static final String LOG_TAG_RECEIVED_POINTER_TRACKER = "ReceivedPointerTracker";
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001731
1732 // The coefficient by which to multiply
1733 // ViewConfiguration.#getScaledTouchSlop()
1734 // to compute #mThresholdActivePointer.
1735 private static final int COEFFICIENT_ACTIVE_POINTER = 2;
1736
1737 // Pointers that moved less than mThresholdActivePointer
1738 // are considered active i.e. are ignored.
1739 private final double mThresholdActivePointer;
1740
1741 // Keep track of where and when a pointer went down.
1742 private final float[] mReceivedPointerDownX = new float[MAX_POINTER_COUNT];
1743 private final float[] mReceivedPointerDownY = new float[MAX_POINTER_COUNT];
1744 private final long[] mReceivedPointerDownTime = new long[MAX_POINTER_COUNT];
1745
1746 // Which pointers are down.
1747 private int mReceivedPointersDown;
1748
1749 // Which down pointers are active.
1750 private int mActivePointers;
1751
1752 // Primary active pointer which is either the first that went down
1753 // or if it goes up the next active that most recently went down.
1754 private int mPrimaryActivePointerId;
1755
1756 // Flag indicating that there is at least one active pointer moving.
1757 private boolean mHasMovingActivePointer;
1758
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001759 // Keep track of the last up pointer data.
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001760 private long mLastReceivedUpPointerDownTime;
1761 private int mLastReceivedUpPointerId;
1762 private boolean mLastReceivedUpPointerActive;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001763 private float mLastReceivedUpPointerDownX;
1764 private float mLastReceivedUpPointerDownY;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001765
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001766 private MotionEvent mLastReceivedEvent;
1767
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001768 /**
1769 * Creates a new instance.
1770 *
1771 * @param context Context for looking up resources.
1772 */
Svetoslav Ganov42138042012-03-20 11:51:39 -07001773 public ReceivedPointerTracker(Context context) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001774 mThresholdActivePointer =
Svetoslav Ganov76c0dd42012-09-24 19:16:16 -07001775 ViewConfiguration.get(context).getScaledTouchSlop() * COEFFICIENT_ACTIVE_POINTER;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001776 }
1777
1778 /**
1779 * Clears the internals state.
1780 */
1781 public void clear() {
1782 Arrays.fill(mReceivedPointerDownX, 0);
1783 Arrays.fill(mReceivedPointerDownY, 0);
1784 Arrays.fill(mReceivedPointerDownTime, 0);
1785 mReceivedPointersDown = 0;
1786 mActivePointers = 0;
1787 mPrimaryActivePointerId = 0;
1788 mHasMovingActivePointer = false;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001789 mLastReceivedUpPointerDownTime = 0;
1790 mLastReceivedUpPointerId = 0;
1791 mLastReceivedUpPointerActive = false;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001792 mLastReceivedUpPointerDownX = 0;
1793 mLastReceivedUpPointerDownY = 0;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001794 }
1795
1796 /**
1797 * Processes a received {@link MotionEvent} event.
1798 *
1799 * @param event The event to process.
1800 */
Svetoslav Ganov42138042012-03-20 11:51:39 -07001801 public void onMotionEvent(MotionEvent event) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001802 if (mLastReceivedEvent != null) {
1803 mLastReceivedEvent.recycle();
1804 }
1805 mLastReceivedEvent = MotionEvent.obtain(event);
1806
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001807 final int action = event.getActionMasked();
1808 switch (action) {
1809 case MotionEvent.ACTION_DOWN: {
Svetoslav Ganov00f7b3f2011-06-07 19:24:10 -07001810 handleReceivedPointerDown(event.getActionIndex(), event);
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001811 } break;
1812 case MotionEvent.ACTION_POINTER_DOWN: {
1813 handleReceivedPointerDown(event.getActionIndex(), event);
1814 } break;
1815 case MotionEvent.ACTION_MOVE: {
1816 handleReceivedPointerMove(event);
1817 } break;
1818 case MotionEvent.ACTION_UP: {
Svetoslav Ganov00f7b3f2011-06-07 19:24:10 -07001819 handleReceivedPointerUp(event.getActionIndex(), event);
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001820 } break;
1821 case MotionEvent.ACTION_POINTER_UP: {
1822 handleReceivedPointerUp(event.getActionIndex(), event);
1823 } break;
1824 }
1825 if (DEBUG) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001826 Slog.i(LOG_TAG_RECEIVED_POINTER_TRACKER, "Received pointer: " + toString());
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001827 }
1828 }
1829
1830 /**
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001831 * @return The last received event.
1832 */
1833 public MotionEvent getLastReceivedEvent() {
1834 return mLastReceivedEvent;
1835 }
1836
1837 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001838 * @return The number of received pointers that are down.
1839 */
1840 public int getReceivedPointerDownCount() {
1841 return Integer.bitCount(mReceivedPointersDown);
1842 }
1843
1844 /**
Svetoslav Ganov42138042012-03-20 11:51:39 -07001845 * @return The bits of the pointers that are active.
1846 */
1847 public int getActivePointers() {
1848 return mActivePointers;
1849 }
1850
1851 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001852 * @return The number of down input pointers that are active.
1853 */
1854 public int getActivePointerCount() {
1855 return Integer.bitCount(mActivePointers);
1856 }
1857
1858 /**
1859 * Whether an received pointer is down.
1860 *
1861 * @param pointerId The unique pointer id.
1862 * @return True if the pointer is down.
1863 */
1864 public boolean isReceivedPointerDown(int pointerId) {
1865 final int pointerFlag = (1 << pointerId);
1866 return (mReceivedPointersDown & pointerFlag) != 0;
1867 }
1868
1869 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001870 * Whether an input pointer is active.
1871 *
1872 * @param pointerId The unique pointer id.
1873 * @return True if the pointer is active.
1874 */
1875 public boolean isActivePointer(int pointerId) {
1876 final int pointerFlag = (1 << pointerId);
1877 return (mActivePointers & pointerFlag) != 0;
1878 }
1879
1880 /**
1881 * @param pointerId The unique pointer id.
1882 * @return The X coordinate where the pointer went down.
1883 */
1884 public float getReceivedPointerDownX(int pointerId) {
1885 return mReceivedPointerDownX[pointerId];
1886 }
1887
1888 /**
1889 * @param pointerId The unique pointer id.
1890 * @return The Y coordinate where the pointer went down.
1891 */
1892 public float getReceivedPointerDownY(int pointerId) {
1893 return mReceivedPointerDownY[pointerId];
1894 }
1895
1896 /**
1897 * @param pointerId The unique pointer id.
1898 * @return The time when the pointer went down.
1899 */
1900 public long getReceivedPointerDownTime(int pointerId) {
1901 return mReceivedPointerDownTime[pointerId];
1902 }
1903
1904 /**
1905 * @return The id of the primary pointer.
1906 */
1907 public int getPrimaryActivePointerId() {
1908 if (mPrimaryActivePointerId == INVALID_POINTER_ID) {
1909 mPrimaryActivePointerId = findPrimaryActivePointer();
1910 }
1911 return mPrimaryActivePointerId;
1912 }
1913
1914 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001915 * @return The time when the last up received pointer went down.
1916 */
1917 public long getLastReceivedUpPointerDownTime() {
1918 return mLastReceivedUpPointerDownTime;
1919 }
1920
1921 /**
1922 * @return The id of the last received pointer that went up.
1923 */
1924 public int getLastReceivedUpPointerId() {
1925 return mLastReceivedUpPointerId;
1926 }
1927
Svetoslav Ganov42138042012-03-20 11:51:39 -07001928
1929 /**
1930 * @return The down X of the last received pointer that went up.
1931 */
1932 public float getLastReceivedUpPointerDownX() {
1933 return mLastReceivedUpPointerDownX;
1934 }
1935
1936 /**
1937 * @return The down Y of the last received pointer that went up.
1938 */
1939 public float getLastReceivedUpPointerDownY() {
1940 return mLastReceivedUpPointerDownY;
1941 }
1942
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001943 /**
1944 * @return Whether the last received pointer that went up was active.
1945 */
1946 public boolean wasLastReceivedUpPointerActive() {
1947 return mLastReceivedUpPointerActive;
1948 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001949 /**
1950 * Populates the active pointer IDs to the given array.
1951 * <p>
1952 * Note: The client is responsible for providing large enough array.
1953 *
1954 * @param outPointerIds The array to which to write the active pointers.
1955 */
1956 public void populateActivePointerIds(int[] outPointerIds) {
1957 int index = 0;
1958 for (int idBits = mActivePointers; idBits != 0; ) {
1959 final int id = Integer.numberOfTrailingZeros(idBits);
1960 idBits &= ~(1 << id);
1961 outPointerIds[index] = id;
1962 index++;
1963 }
1964 }
1965
1966 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001967 * @param pointerId The unique pointer id.
1968 * @return Whether the pointer is active or was the last active than went up.
1969 */
Svetoslav Ganov42138042012-03-20 11:51:39 -07001970 public boolean isActiveOrWasLastActiveUpPointer(int pointerId) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001971 return (isActivePointer(pointerId)
1972 || (mLastReceivedUpPointerId == pointerId
1973 && mLastReceivedUpPointerActive));
1974 }
1975
1976 /**
1977 * Handles a received pointer down event.
1978 *
1979 * @param pointerIndex The index of the pointer that has changed.
1980 * @param event The event to be handled.
1981 */
1982 private void handleReceivedPointerDown(int pointerIndex, MotionEvent event) {
1983 final int pointerId = event.getPointerId(pointerIndex);
1984 final int pointerFlag = (1 << pointerId);
1985
1986 mLastReceivedUpPointerId = 0;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001987 mLastReceivedUpPointerDownTime = 0;
1988 mLastReceivedUpPointerActive = false;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001989 mLastReceivedUpPointerDownX = 0;
1990 mLastReceivedUpPointerDownX = 0;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001991
1992 mReceivedPointersDown |= pointerFlag;
1993 mReceivedPointerDownX[pointerId] = event.getX(pointerIndex);
1994 mReceivedPointerDownY[pointerId] = event.getY(pointerIndex);
1995 mReceivedPointerDownTime[pointerId] = event.getEventTime();
1996
1997 if (!mHasMovingActivePointer) {
1998 // If still no moving active pointers every
1999 // down pointer is the only active one.
2000 mActivePointers = pointerFlag;
2001 mPrimaryActivePointerId = pointerId;
2002 } else {
2003 // If at least one moving active pointer every
2004 // subsequent down pointer is active.
2005 mActivePointers |= pointerFlag;
2006 }
2007 }
2008
2009 /**
2010 * Handles a received pointer move event.
2011 *
2012 * @param event The event to be handled.
2013 */
2014 private void handleReceivedPointerMove(MotionEvent event) {
2015 detectActivePointers(event);
2016 }
2017
2018 /**
2019 * Handles a received pointer up event.
2020 *
2021 * @param pointerIndex The index of the pointer that has changed.
2022 * @param event The event to be handled.
2023 */
2024 private void handleReceivedPointerUp(int pointerIndex, MotionEvent event) {
2025 final int pointerId = event.getPointerId(pointerIndex);
2026 final int pointerFlag = (1 << pointerId);
2027
2028 mLastReceivedUpPointerId = pointerId;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07002029 mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId);
2030 mLastReceivedUpPointerActive = isActivePointer(pointerId);
Svetoslav Ganov42138042012-03-20 11:51:39 -07002031 mLastReceivedUpPointerDownX = mReceivedPointerDownX[pointerId];
2032 mLastReceivedUpPointerDownY = mReceivedPointerDownY[pointerId];
Svetoslav Ganov736c2752011-04-22 18:30:36 -07002033
2034 mReceivedPointersDown &= ~pointerFlag;
2035 mActivePointers &= ~pointerFlag;
2036 mReceivedPointerDownX[pointerId] = 0;
2037 mReceivedPointerDownY[pointerId] = 0;
2038 mReceivedPointerDownTime[pointerId] = 0;
2039
2040 if (mActivePointers == 0) {
2041 mHasMovingActivePointer = false;
2042 }
2043 if (mPrimaryActivePointerId == pointerId) {
2044 mPrimaryActivePointerId = INVALID_POINTER_ID;
2045 }
2046 }
2047
2048 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -07002049 * Detects the active pointers in an event.
2050 *
2051 * @param event The event to examine.
2052 */
2053 private void detectActivePointers(MotionEvent event) {
2054 for (int i = 0, count = event.getPointerCount(); i < count; i++) {
2055 final int pointerId = event.getPointerId(i);
2056 if (mHasMovingActivePointer) {
2057 // If already active => nothing to do.
2058 if (isActivePointer(pointerId)) {
2059 continue;
2060 }
2061 }
2062 // Active pointers are ones that moved more than a given threshold.
2063 final float pointerDeltaMove = computePointerDeltaMove(i, event);
2064 if (pointerDeltaMove > mThresholdActivePointer) {
2065 final int pointerFlag = (1 << pointerId);
2066 mActivePointers |= pointerFlag;
2067 mHasMovingActivePointer = true;
2068 }
2069 }
2070 }
2071
2072 /**
2073 * @return The primary active pointer.
2074 */
2075 private int findPrimaryActivePointer() {
2076 int primaryActivePointerId = INVALID_POINTER_ID;
2077 long minDownTime = Long.MAX_VALUE;
2078 // Find the active pointer that went down first.
2079 for (int i = 0, count = mReceivedPointerDownTime.length; i < count; i++) {
2080 if (isActivePointer(i)) {
2081 final long downPointerTime = mReceivedPointerDownTime[i];
2082 if (downPointerTime < minDownTime) {
2083 minDownTime = downPointerTime;
2084 primaryActivePointerId = i;
2085 }
2086 }
2087 }
2088 return primaryActivePointerId;
2089 }
2090
2091 /**
2092 * Computes the move for a given action pointer index since the
2093 * corresponding pointer went down.
2094 *
2095 * @param pointerIndex The action pointer index.
2096 * @param event The event to examine.
2097 * @return The distance the pointer has moved.
2098 */
2099 private float computePointerDeltaMove(int pointerIndex, MotionEvent event) {
2100 final int pointerId = event.getPointerId(pointerIndex);
2101 final float deltaX = event.getX(pointerIndex) - mReceivedPointerDownX[pointerId];
2102 final float deltaY = event.getY(pointerIndex) - mReceivedPointerDownY[pointerId];
2103 return (float) Math.hypot(deltaX, deltaY);
2104 }
2105
2106 @Override
2107 public String toString() {
2108 StringBuilder builder = new StringBuilder();
2109 builder.append("=========================");
2110 builder.append("\nDown pointers #");
2111 builder.append(getReceivedPointerDownCount());
2112 builder.append(" [ ");
2113 for (int i = 0; i < MAX_POINTER_COUNT; i++) {
2114 if (isReceivedPointerDown(i)) {
2115 builder.append(i);
2116 builder.append(" ");
2117 }
2118 }
2119 builder.append("]");
2120 builder.append("\nActive pointers #");
2121 builder.append(getActivePointerCount());
2122 builder.append(" [ ");
2123 for (int i = 0; i < MAX_POINTER_COUNT; i++) {
2124 if (isActivePointer(i)) {
2125 builder.append(i);
2126 builder.append(" ");
2127 }
2128 }
2129 builder.append("]");
2130 builder.append("\nPrimary active pointer id [ ");
2131 builder.append(getPrimaryActivePointerId());
2132 builder.append(" ]");
2133 builder.append("\n=========================");
2134 return builder.toString();
2135 }
2136 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -07002137}