blob: 26887767028a1c151b81908a3b8ab5bb5759c6a0 [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 Ganovf772cba2012-10-05 18:49:17 -0700307 mAms.onTouchInteractionEnd();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700308 }
309
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -0700310 @Override
311 public void setNext(EventStreamTransformation next) {
312 mNext = next;
313 }
314
315 @Override
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700316 public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700317 if (DEBUG) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700318 Slog.d(LOG_TAG, "Received event: " + event + ", policyFlags=0x"
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700319 + Integer.toHexString(policyFlags));
Svetoslav Ganov42138042012-03-20 11:51:39 -0700320 Slog.d(LOG_TAG, getStateSymbolicName(mCurrentState));
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700321 }
322
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700323 mReceivedPointerTracker.onMotionEvent(rawEvent);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700324
325 switch(mCurrentState) {
326 case STATE_TOUCH_EXPLORING: {
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700327 handleMotionEventStateTouchExploring(event, rawEvent, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700328 } break;
329 case STATE_DRAGGING: {
330 handleMotionEventStateDragging(event, policyFlags);
331 } break;
332 case STATE_DELEGATING: {
333 handleMotionEventStateDelegating(event, policyFlags);
334 } break;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700335 case STATE_GESTURE_DETECTING: {
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700336 handleMotionEventGestureDetecting(rawEvent, policyFlags);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700337 } break;
338 default:
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700339 throw new IllegalStateException("Illegal state: " + mCurrentState);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700340 }
341 }
342
Svetoslav Ganov86783472012-06-06 21:12:20 -0700343 public void onAccessibilityEvent(AccessibilityEvent event) {
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700344 final int eventType = event.getEventType();
345
346 // The event for gesture end should be strictly after the
347 // last hover exit event.
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700348 if (mSendTouchExplorationEndDelayed.isPending()
Svetoslav Ganov8b681cb2012-09-14 15:20:45 -0700349 && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700350 mSendTouchExplorationEndDelayed.remove();
Svetoslav Ganov8b681cb2012-09-14 15:20:45 -0700351 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END);
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700352 }
353
354 // The event for touch interaction end should be strictly after the
355 // last hover exit and the touch exploration gesture end events.
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700356 if (mSendTouchInteractionEndDelayed.isPending()
Svetoslav Ganov8b681cb2012-09-14 15:20:45 -0700357 && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) {
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700358 mSendTouchInteractionEndDelayed.remove();
Svetoslav Ganov8b681cb2012-09-14 15:20:45 -0700359 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700360 }
361
Svetoslav Ganov86783472012-06-06 21:12:20 -0700362 // If a new window opens or the accessibility focus moves we no longer
363 // want to click/long press on the last touch explored location.
Svetoslav Ganov86783472012-06-06 21:12:20 -0700364 switch (eventType) {
365 case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
366 case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED: {
Svetoslav Ganov5d043ce2012-06-14 10:30:00 -0700367 if (mInjectedPointerTracker.mLastInjectedHoverEventForClick != null) {
368 mInjectedPointerTracker.mLastInjectedHoverEventForClick.recycle();
369 mInjectedPointerTracker.mLastInjectedHoverEventForClick = null;
Svetoslav Ganov86783472012-06-06 21:12:20 -0700370 }
Svetoslav Ganov385d9f22012-06-07 16:35:04 -0700371 mLastTouchedWindowId = -1;
372 } break;
373 case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
374 case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT: {
375 mLastTouchedWindowId = event.getWindowId();
Svetoslav Ganov86783472012-06-06 21:12:20 -0700376 } break;
377 }
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -0700378 if (mNext != null) {
379 mNext.onAccessibilityEvent(event);
380 }
Svetoslav Ganov86783472012-06-06 21:12:20 -0700381 }
382
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700383 /**
384 * Handles a motion event in touch exploring state.
385 *
386 * @param event The event to be handled.
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700387 * @param rawEvent The raw (unmodified) motion event.
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700388 * @param policyFlags The policy flags associated with the event.
389 */
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700390 private void handleMotionEventStateTouchExploring(MotionEvent event, MotionEvent rawEvent,
391 int policyFlags) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700392 ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
Svetoslav Ganov42138042012-03-20 11:51:39 -0700393 final int activePointerCount = receivedTracker.getActivePointerCount();
394
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700395 mVelocityTracker.addMovement(rawEvent);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700396
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700397 mDoubleTapDetector.onMotionEvent(event, policyFlags);
398
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700399 switch (event.getActionMasked()) {
Svetoslav Ganovf5a07902011-07-24 19:20:17 -0700400 case MotionEvent.ACTION_DOWN:
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700401 // Pre-feed the motion events to the gesture detector since we
402 // have a distance slop before getting into gesture detection
403 // mode and not using the points within this slop significantly
404 // decreases the quality of gesture recognition.
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700405 handleMotionEventGestureDetecting(rawEvent, policyFlags);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700406 //$FALL-THROUGH$
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700407 case MotionEvent.ACTION_POINTER_DOWN: {
408 switch (activePointerCount) {
409 case 0: {
410 throw new IllegalStateException("The must always be one active pointer in"
411 + "touch exploring state!");
412 }
413 case 1: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700414 // If we still have not notified the user for the last
415 // touch, we figure out what to do. If were waiting
416 // we resent the delayed callback and wait again.
417 if (mSendHoverEnterDelayed.isPending()) {
418 mSendHoverEnterDelayed.remove();
419 mSendHoverExitDelayed.remove();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700420 }
421
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700422 if (mSendTouchExplorationEndDelayed.isPending()) {
423 mSendTouchExplorationEndDelayed.forceSendAndRemove();
424 }
425
426 if (mSendTouchInteractionEndDelayed.isPending()) {
427 mSendTouchInteractionEndDelayed.forceSendAndRemove();
428 }
429
430 // Every pointer that goes down is active until it moves or
431 // another one goes down. Hence, having more than one pointer
432 // down we have already send the interaction start event.
433 if (event.getPointerCount() == 1) {
434 sendAccessibilityEvent(
435 AccessibilityEvent.TYPE_TOUCH_INTERACTION_START);
Svetoslav Ganovfe304b82012-09-28 11:12:34 -0700436 }
437
Svetoslav Ganovebac1b72012-06-02 16:26:44 -0700438 mPerformLongPressDelayed.remove();
439
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700440 // If we have the first tap schedule a long press and break
441 // since we do not want to schedule hover enter because
442 // the delayed callback will kick in before the long click.
443 // This would lead to a state transition resulting in long
444 // pressing the item below the double taped area which is
445 // not necessary where accessibility focus is.
446 if (mDoubleTapDetector.firstTapDetected()) {
447 // We got a tap now post a long press action.
448 mPerformLongPressDelayed.post(event, policyFlags);
449 break;
450 }
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700451 if (!mTouchExplorationInProgress) {
452 // Deliver hover enter with a delay to have a chance
453 // to detect what the user is trying to do.
454 final int pointerId = receivedTracker.getPrimaryActivePointerId();
455 final int pointerIdBits = (1 << pointerId);
456 mSendHoverEnterDelayed.post(event, true, pointerIdBits, policyFlags);
457 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700458 } break;
459 default: {
460 /* do nothing - let the code for ACTION_MOVE decide what to do */
461 } break;
462 }
463 } break;
464 case MotionEvent.ACTION_MOVE: {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700465 final int pointerId = receivedTracker.getPrimaryActivePointerId();
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700466 final int pointerIndex = event.findPointerIndex(pointerId);
467 final int pointerIdBits = (1 << pointerId);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700468 switch (activePointerCount) {
469 case 0: {
470 /* do nothing - no active pointers so we swallow the event */
471 } break;
472 case 1: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700473 // We have not started sending events since we try to
474 // figure out what the user is doing.
475 if (mSendHoverEnterDelayed.isPending()) {
476 // Pre-feed the motion events to the gesture detector since we
477 // have a distance slop before getting into gesture detection
478 // mode and not using the points within this slop significantly
479 // decreases the quality of gesture recognition.
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700480 handleMotionEventGestureDetecting(rawEvent, policyFlags);
481 // It is *important* to use the distance traveled by the pointers
482 // on the screen which may or may not be magnified.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700483 final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700484 - rawEvent.getX(pointerIndex);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700485 final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700486 - rawEvent.getY(pointerIndex);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700487 final double moveDelta = Math.hypot(deltaX, deltaY);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700488 // The user has moved enough for us to decide.
489 if (moveDelta > mDoubleTapSlop) {
490 // Check whether the user is performing a gesture. We
491 // detect gestures if the pointer is moving above a
492 // given velocity.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700493 mVelocityTracker.computeCurrentVelocity(1000);
494 final float maxAbsVelocity = Math.max(
495 Math.abs(mVelocityTracker.getXVelocity(pointerId)),
496 Math.abs(mVelocityTracker.getYVelocity(pointerId)));
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700497 if (maxAbsVelocity > mScaledGestureDetectionVelocity) {
498 // We have to perform gesture detection, so
499 // clear the current state and try to detect.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700500 mCurrentState = STATE_GESTURE_DETECTING;
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700501 mVelocityTracker.clear();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700502 mSendHoverEnterDelayed.remove();
503 mSendHoverExitDelayed.remove();
504 mPerformLongPressDelayed.remove();
Svetoslav Ganov95068e52012-06-13 21:01:51 -0700505 mExitGestureDetectionModeDelayed.post();
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700506 // Send accessibility event to announce the start
507 // of gesture recognition.
508 sendAccessibilityEvent(
509 AccessibilityEvent.TYPE_GESTURE_DETECTION_START);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700510 } else {
511 // We have just decided that the user is touch,
512 // exploring so start sending events.
513 mSendHoverEnterDelayed.forceSendAndRemove();
514 mSendHoverExitDelayed.remove();
Svetoslav Ganovebac1b72012-06-02 16:26:44 -0700515 mPerformLongPressDelayed.remove();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700516 sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE,
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700517 pointerIdBits, policyFlags);
518 }
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700519 break;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700520 }
521 } else {
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700522 // Cancel the long press if pending and the user
523 // moved more than the slop.
524 if (mPerformLongPressDelayed.isPending()) {
525 final float deltaX =
526 receivedTracker.getReceivedPointerDownX(pointerId)
527 - rawEvent.getX(pointerIndex);
528 final float deltaY =
529 receivedTracker.getReceivedPointerDownY(pointerId)
530 - rawEvent.getY(pointerIndex);
531 final double moveDelta = Math.hypot(deltaX, deltaY);
532 // The user has moved enough for us to decide.
533 if (moveDelta > mTouchSlop) {
534 mPerformLongPressDelayed.remove();
535 }
536 }
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700537 // The user is wither double tapping or performing long
538 // press so do not send move events yet.
539 if (mDoubleTapDetector.firstTapDetected()) {
540 break;
541 }
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700542 sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700543 sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits,
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700544 policyFlags);
545 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700546 } break;
547 case 2: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700548 // More than one pointer so the user is not touch exploring
549 // and now we have to decide whether to delegate or drag.
550 if (mSendHoverEnterDelayed.isPending()) {
551 // We have not started sending events so cancel
552 // scheduled sending events.
553 mSendHoverEnterDelayed.remove();
554 mSendHoverExitDelayed.remove();
555 mPerformLongPressDelayed.remove();
556 } else {
Svetoslav Ganovebac1b72012-06-02 16:26:44 -0700557 mPerformLongPressDelayed.remove();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700558 // If the user is touch exploring the second pointer may be
559 // performing a double tap to activate an item without need
560 // for the user to lift his exploring finger.
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700561 // It is *important* to use the distance traveled by the pointers
562 // on the screen which may or may not be magnified.
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700563 final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId)
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700564 - rawEvent.getX(pointerIndex);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700565 final float deltaY = receivedTracker.getReceivedPointerDownY(pointerId)
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700566 - rawEvent.getY(pointerIndex);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700567 final double moveDelta = Math.hypot(deltaX, deltaY);
568 if (moveDelta < mDoubleTapSlop) {
569 break;
570 }
571 // We are sending events so send exit and gesture
572 // end since we transition to another state.
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700573 sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700574 }
575
576 // We know that a new state transition is to happen and the
577 // new state will not be gesture recognition, so clear the
578 // stashed gesture strokes.
579 mStrokeBuffer.clear();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700580
581 if (isDraggingGesture(event)) {
582 // Two pointers moving in the same direction within
583 // a given distance perform a drag.
Svetoslav Ganov00f7b3f2011-06-07 19:24:10 -0700584 mCurrentState = STATE_DRAGGING;
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700585 mDraggingPointerId = pointerId;
586 sendMotionEvent(event, MotionEvent.ACTION_DOWN, pointerIdBits,
587 policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700588 } else {
589 // Two pointers moving arbitrary are delegated to the view hierarchy.
590 mCurrentState = STATE_DELEGATING;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700591 sendDownForAllActiveNotInjectedPointers(event, policyFlags);
592 }
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700593 mVelocityTracker.clear();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700594 } break;
595 default: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700596 // More than one pointer so the user is not touch exploring
597 // and now we have to decide whether to delegate or drag.
598 if (mSendHoverEnterDelayed.isPending()) {
599 // We have not started sending events so cancel
600 // scheduled sending events.
601 mSendHoverEnterDelayed.remove();
602 mSendHoverExitDelayed.remove();
603 mPerformLongPressDelayed.remove();
604 } else {
Svetoslav Ganovebac1b72012-06-02 16:26:44 -0700605 mPerformLongPressDelayed.remove();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700606 // We are sending events so send exit and gesture
607 // end since we transition to another state.
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700608 sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700609 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700610
611 // More than two pointers are delegated to the view hierarchy.
612 mCurrentState = STATE_DELEGATING;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700613 sendDownForAllActiveNotInjectedPointers(event, policyFlags);
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700614 mVelocityTracker.clear();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700615 }
616 }
617 } break;
618 case MotionEvent.ACTION_UP:
Svetoslav Ganovf772cba2012-10-05 18:49:17 -0700619 mAms.onTouchInteractionEnd();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700620 // We know that we do not need the pre-fed gesture points are not
621 // needed anymore since the last pointer just went up.
622 mStrokeBuffer.clear();
623 //$FALL-THROUGH$
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700624 case MotionEvent.ACTION_POINTER_UP: {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700625 final int pointerId = receivedTracker.getLastReceivedUpPointerId();
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700626 final int pointerIdBits = (1 << pointerId);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700627 switch (activePointerCount) {
628 case 0: {
629 // If the pointer that went up was not active we have nothing to do.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700630 if (!receivedTracker.wasLastReceivedUpPointerActive()) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700631 break;
632 }
633
Svetoslav Ganovf5a07902011-07-24 19:20:17 -0700634 mPerformLongPressDelayed.remove();
635
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700636 // If we have not delivered the enter schedule exit.
637 if (mSendHoverEnterDelayed.isPending()) {
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700638 mSendHoverExitDelayed.post(event, false, pointerIdBits, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700639 } else {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700640 // The user is touch exploring so we send events for end.
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700641 sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags);
642 }
643
644 if (!mSendTouchInteractionEndDelayed.isPending()) {
645 mSendTouchInteractionEndDelayed.post();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700646 }
647 } break;
648 }
Svetoslav Ganov45af84a2012-10-01 16:25:08 -0700649 mVelocityTracker.clear();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700650 } break;
651 case MotionEvent.ACTION_CANCEL: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700652 clear(event, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700653 } break;
654 }
655 }
656
657 /**
658 * Handles a motion event in dragging state.
659 *
660 * @param event The event to be handled.
661 * @param policyFlags The policy flags associated with the event.
662 */
663 private void handleMotionEventStateDragging(MotionEvent event, int policyFlags) {
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700664 final int pointerIdBits = (1 << mDraggingPointerId);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700665 switch (event.getActionMasked()) {
666 case MotionEvent.ACTION_DOWN: {
667 throw new IllegalStateException("Dragging state can be reached only if two "
668 + "pointers are already down");
669 }
670 case MotionEvent.ACTION_POINTER_DOWN: {
671 // We are in dragging state so we have two pointers and another one
672 // goes down => delegate the three pointers to the view hierarchy
673 mCurrentState = STATE_DELEGATING;
Svetoslav Ganovec33d562012-10-03 17:00:18 -0700674 if (mDraggingPointerId != INVALID_POINTER_ID) {
675 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
676 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700677 sendDownForAllActiveNotInjectedPointers(event, policyFlags);
678 } break;
679 case MotionEvent.ACTION_MOVE: {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700680 final int activePointerCount = mReceivedPointerTracker.getActivePointerCount();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700681 switch (activePointerCount) {
Svetoslav Ganov2e1c66b2011-10-11 18:19:10 -0700682 case 1: {
683 // do nothing
684 } break;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700685 case 2: {
686 if (isDraggingGesture(event)) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700687 // If the dragging pointer are closer that a given distance we
688 // use the location of the primary one. Otherwise, we take the
689 // middle between the pointers.
690 int[] pointerIds = mTempPointerIds;
691 mReceivedPointerTracker.populateActivePointerIds(pointerIds);
692
693 final int firstPtrIndex = event.findPointerIndex(pointerIds[0]);
694 final int secondPtrIndex = event.findPointerIndex(pointerIds[1]);
695
696 final float firstPtrX = event.getX(firstPtrIndex);
697 final float firstPtrY = event.getY(firstPtrIndex);
698 final float secondPtrX = event.getX(secondPtrIndex);
699 final float secondPtrY = event.getY(secondPtrIndex);
700
701 final float deltaX = firstPtrX - secondPtrX;
702 final float deltaY = firstPtrY - secondPtrY;
703 final double distance = Math.hypot(deltaX, deltaY);
704
705 if (distance > mScaledMinPointerDistanceToUseMiddleLocation) {
706 event.setLocation(deltaX / 2, deltaY / 2);
707 }
708
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700709 // If still dragging send a drag event.
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700710 sendMotionEvent(event, MotionEvent.ACTION_MOVE, pointerIdBits,
711 policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700712 } else {
713 // The two pointers are moving either in different directions or
714 // no close enough => delegate the gesture to the view hierarchy.
715 mCurrentState = STATE_DELEGATING;
716 // Send an event to the end of the drag gesture.
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700717 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
718 policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700719 // Deliver all active pointers to the view hierarchy.
720 sendDownForAllActiveNotInjectedPointers(event, policyFlags);
721 }
722 } break;
723 default: {
724 mCurrentState = STATE_DELEGATING;
725 // Send an event to the end of the drag gesture.
Svetoslav Ganov91feae32011-05-19 18:16:31 -0700726 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits,
727 policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700728 // Deliver all active pointers to the view hierarchy.
729 sendDownForAllActiveNotInjectedPointers(event, policyFlags);
730 }
731 }
732 } break;
Svetoslav Ganovaeb8d0e2012-10-02 14:16:08 -0700733 case MotionEvent.ACTION_POINTER_UP: {
734 final int pointerId = event.getPointerId(event.getActionIndex());
735 if (pointerId == mDraggingPointerId) {
736 mDraggingPointerId = INVALID_POINTER_ID;
737 // Send an event to the end of the drag gesture.
738 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
739 }
740 } break;
Svetoslav Ganov2e1c66b2011-10-11 18:19:10 -0700741 case MotionEvent.ACTION_UP: {
Svetoslav Ganovf772cba2012-10-05 18:49:17 -0700742 mAms.onTouchInteractionEnd();
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700743 // Announce the end of a new touch interaction.
744 sendAccessibilityEvent(
745 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
Svetoslav Ganovaeb8d0e2012-10-02 14:16:08 -0700746 final int pointerId = event.getPointerId(event.getActionIndex());
747 if (pointerId == mDraggingPointerId) {
748 mDraggingPointerId = INVALID_POINTER_ID;
749 // Send an event to the end of the drag gesture.
750 sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
751 }
Svetoslav Ganov2e1c66b2011-10-11 18:19:10 -0700752 mCurrentState = STATE_TOUCH_EXPLORING;
753 } break;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700754 case MotionEvent.ACTION_CANCEL: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700755 clear(event, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700756 } break;
757 }
758 }
759
760 /**
761 * Handles a motion event in delegating state.
762 *
763 * @param event The event to be handled.
764 * @param policyFlags The policy flags associated with the event.
765 */
Svetoslav Ganov2e1c66b2011-10-11 18:19:10 -0700766 private void handleMotionEventStateDelegating(MotionEvent event, int policyFlags) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700767 switch (event.getActionMasked()) {
768 case MotionEvent.ACTION_DOWN: {
769 throw new IllegalStateException("Delegating state can only be reached if "
770 + "there is at least one pointer down!");
771 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700772 case MotionEvent.ACTION_MOVE: {
773 // Check whether some other pointer became active because they have moved
774 // a given distance and if such exist send them to the view hierarchy
Svetoslav Ganov42138042012-03-20 11:51:39 -0700775 final int notInjectedCount = getNotInjectedActivePointerCount(
776 mReceivedPointerTracker, mInjectedPointerTracker);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700777 if (notInjectedCount > 0) {
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700778 MotionEvent prototype = MotionEvent.obtain(event);
779 sendDownForAllActiveNotInjectedPointers(prototype, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700780 }
781 } break;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700782 case MotionEvent.ACTION_UP:
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700783 // Announce the end of a new touch interaction.
784 sendAccessibilityEvent(
785 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
786 //$FALL-THROUGH$
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700787 case MotionEvent.ACTION_POINTER_UP: {
Svetoslav Ganovf772cba2012-10-05 18:49:17 -0700788 mAms.onTouchInteractionEnd();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700789 mLongPressingPointerId = -1;
790 mLongPressingPointerDeltaX = 0;
791 mLongPressingPointerDeltaY = 0;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700792 // No active pointers => go to initial state.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700793 if (mReceivedPointerTracker.getActivePointerCount() == 0) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700794 mCurrentState = STATE_TOUCH_EXPLORING;
795 }
796 } break;
797 case MotionEvent.ACTION_CANCEL: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700798 clear(event, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700799 } break;
800 }
801 // Deliver the event striping out inactive pointers.
802 sendMotionEventStripInactivePointers(event, policyFlags);
803 }
804
Svetoslav Ganov42138042012-03-20 11:51:39 -0700805 private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) {
806 switch (event.getActionMasked()) {
807 case MotionEvent.ACTION_DOWN: {
808 final float x = event.getX();
809 final float y = event.getY();
810 mPreviousX = x;
811 mPreviousY = y;
812 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
813 } break;
814 case MotionEvent.ACTION_MOVE: {
815 final float x = event.getX();
816 final float y = event.getY();
817 final float dX = Math.abs(x - mPreviousX);
818 final float dY = Math.abs(y - mPreviousY);
819 if (dX >= TOUCH_TOLERANCE || dY >= TOUCH_TOLERANCE) {
820 mPreviousX = x;
821 mPreviousY = y;
822 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
823 }
824 } break;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700825 case MotionEvent.ACTION_UP: {
Svetoslav Ganovf772cba2012-10-05 18:49:17 -0700826 mAms.onTouchInteractionEnd();
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700827 // Announce the end of gesture recognition.
828 sendAccessibilityEvent(
829 AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
830 // Announce the end of a new touch interaction.
831 sendAccessibilityEvent(
832 AccessibilityEvent.TYPE_TOUCH_INTERACTION_END);
833
Svetoslav Ganov42138042012-03-20 11:51:39 -0700834 float x = event.getX();
835 float y = event.getY();
836 mStrokeBuffer.add(new GesturePoint(x, y, event.getEventTime()));
837
838 Gesture gesture = new Gesture();
839 gesture.addStroke(new GestureStroke(mStrokeBuffer));
840
841 ArrayList<Prediction> predictions = mGestureLibrary.recognize(gesture);
842 if (!predictions.isEmpty()) {
843 Prediction bestPrediction = predictions.get(0);
844 if (bestPrediction.score >= MIN_PREDICTION_SCORE) {
845 if (DEBUG) {
846 Slog.i(LOG_TAG, "gesture: " + bestPrediction.name + " score: "
847 + bestPrediction.score);
848 }
849 try {
850 final int gestureId = Integer.parseInt(bestPrediction.name);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700851 mAms.onGesture(gestureId);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700852 } catch (NumberFormatException nfe) {
853 Slog.w(LOG_TAG, "Non numeric gesture id:" + bestPrediction.name);
854 }
855 }
856 }
857
858 mStrokeBuffer.clear();
Svetoslav Ganov95068e52012-06-13 21:01:51 -0700859 mExitGestureDetectionModeDelayed.remove();
Svetoslav Ganov42138042012-03-20 11:51:39 -0700860 mCurrentState = STATE_TOUCH_EXPLORING;
861 } break;
862 case MotionEvent.ACTION_CANCEL: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700863 clear(event, policyFlags);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700864 } break;
865 }
866 }
867
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700868 /**
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700869 * Sends an accessibility event of the given type.
870 *
871 * @param type The event type.
872 */
873 private void sendAccessibilityEvent(int type) {
874 AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
875 if (accessibilityManager.isEnabled()) {
876 AccessibilityEvent event = AccessibilityEvent.obtain(type);
877 accessibilityManager.sendAccessibilityEvent(event);
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700878 switch (type) {
879 case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: {
880 mTouchExplorationInProgress = true;
881 } break;
882 case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END: {
883 mTouchExplorationInProgress = false;
884 } break;
885 }
Svetoslav Ganov77276b62012-09-14 10:23:00 -0700886 }
887 }
888
889 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700890 * Sends down events to the view hierarchy for all active pointers which are
891 * not already being delivered i.e. pointers that are not yet injected.
892 *
893 * @param prototype The prototype from which to create the injected events.
894 * @param policyFlags The policy flags associated with the event.
895 */
896 private void sendDownForAllActiveNotInjectedPointers(MotionEvent prototype, int policyFlags) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700897 ReceivedPointerTracker receivedPointers = mReceivedPointerTracker;
898 InjectedPointerTracker injectedPointers = mInjectedPointerTracker;
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700899 int pointerIdBits = 0;
900 final int pointerCount = prototype.getPointerCount();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700901
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700902 // Find which pointers are already injected.
903 for (int i = 0; i < pointerCount; i++) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700904 final int pointerId = prototype.getPointerId(i);
Svetoslav Ganov42138042012-03-20 11:51:39 -0700905 if (injectedPointers.isInjectedPointerDown(pointerId)) {
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700906 pointerIdBits |= (1 << pointerId);
907 }
908 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700909
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700910 // Inject the active and not injected pointers.
911 for (int i = 0; i < pointerCount; i++) {
912 final int pointerId = prototype.getPointerId(i);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700913 // Skip inactive pointers.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700914 if (!receivedPointers.isActivePointer(pointerId)) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700915 continue;
916 }
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700917 // Do not send event for already delivered pointers.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700918 if (injectedPointers.isInjectedPointerDown(pointerId)) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700919 continue;
920 }
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700921 pointerIdBits |= (1 << pointerId);
922 final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
923 sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
924 }
925 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700926
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700927 /**
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700928 * Sends the exit events if needed. Such events are hover exit and touch explore
929 * gesture end.
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700930 *
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700931 * @param policyFlags The policy flags associated with the event.
932 */
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700933 private void sendHoverExitAndTouchExplorationGestureEndIfNeeded(int policyFlags) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700934 MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
935 if (event != null && event.getActionMasked() != MotionEvent.ACTION_HOVER_EXIT) {
936 final int pointerIdBits = event.getPointerIdBits();
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700937 if (!mSendTouchExplorationEndDelayed.isPending()) {
938 mSendTouchExplorationEndDelayed.post();
Svetoslav Ganovfe304b82012-09-28 11:12:34 -0700939 }
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700940 sendMotionEvent(event, MotionEvent.ACTION_HOVER_EXIT, pointerIdBits, policyFlags);
941 }
942 }
943
944 /**
945 * Sends the enter events if needed. Such events are hover enter and touch explore
946 * gesture start.
947 *
948 * @param policyFlags The policy flags associated with the event.
949 */
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700950 private void sendTouchExplorationGestureStartAndHoverEnterIfNeeded(int policyFlags) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700951 MotionEvent event = mInjectedPointerTracker.getLastInjectedHoverEvent();
952 if (event != null && event.getActionMasked() == MotionEvent.ACTION_HOVER_EXIT) {
953 final int pointerIdBits = event.getPointerIdBits();
Svetoslav Ganovf068fed2012-10-03 22:25:07 -0700954 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -0700955 sendMotionEvent(event, MotionEvent.ACTION_HOVER_ENTER, pointerIdBits, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700956 }
957 }
958
959 /**
960 * Sends up events to the view hierarchy for all active pointers which are
961 * already being delivered i.e. pointers that are injected.
962 *
963 * @param prototype The prototype from which to create the injected events.
964 * @param policyFlags The policy flags associated with the event.
965 */
966 private void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700967 final InjectedPointerTracker injectedTracked = mInjectedPointerTracker;
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700968 int pointerIdBits = 0;
969 final int pointerCount = prototype.getPointerCount();
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700970 for (int i = 0; i < pointerCount; i++) {
971 final int pointerId = prototype.getPointerId(i);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700972 // Skip non injected down pointers.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700973 if (!injectedTracked.isInjectedPointerDown(pointerId)) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700974 continue;
975 }
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700976 pointerIdBits |= (1 << pointerId);
977 final int action = computeInjectionAction(MotionEvent.ACTION_UP, i);
978 sendMotionEvent(prototype, action, pointerIdBits, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700979 }
980 }
981
982 /**
983 * Sends a motion event by first stripping the inactive pointers.
984 *
985 * @param prototype The prototype from which to create the injected event.
986 * @param policyFlags The policy flags associated with the event.
987 */
988 private void sendMotionEventStripInactivePointers(MotionEvent prototype, int policyFlags) {
Svetoslav Ganov42138042012-03-20 11:51:39 -0700989 ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700990
991 // All pointers active therefore we just inject the event as is.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700992 if (prototype.getPointerCount() == receivedTracker.getActivePointerCount()) {
Svetoslav Ganovf8044202011-08-26 20:33:33 -0700993 sendMotionEvent(prototype, prototype.getAction(), ALL_POINTER_ID_BITS, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -0700994 return;
995 }
996
997 // No active pointers and the one that just went up was not
998 // active, therefore we have nothing to do.
Svetoslav Ganov42138042012-03-20 11:51:39 -0700999 if (receivedTracker.getActivePointerCount() == 0
1000 && !receivedTracker.wasLastReceivedUpPointerActive()) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001001 return;
1002 }
1003
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001004 // If the action pointer going up/down is not active we have nothing to do.
1005 // However, for moves we keep going to report moves of active pointers.
1006 final int actionMasked = prototype.getActionMasked();
1007 final int actionPointerId = prototype.getPointerId(prototype.getActionIndex());
1008 if (actionMasked != MotionEvent.ACTION_MOVE) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001009 if (!receivedTracker.isActiveOrWasLastActiveUpPointer(actionPointerId)) {
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001010 return;
1011 }
1012 }
1013
1014 // If the pointer is active or the pointer that just went up
1015 // was active we keep the pointer data in the event.
Svetoslav Ganov91feae32011-05-19 18:16:31 -07001016 int pointerIdBits = 0;
1017 final int pointerCount = prototype.getPointerCount();
1018 for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) {
1019 final int pointerId = prototype.getPointerId(pointerIndex);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001020 if (receivedTracker.isActiveOrWasLastActiveUpPointer(pointerId)) {
Svetoslav Ganov91feae32011-05-19 18:16:31 -07001021 pointerIdBits |= (1 << pointerId);
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001022 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001023 }
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001024 sendMotionEvent(prototype, prototype.getAction(), pointerIdBits, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001025 }
1026
1027 /**
1028 * Sends an up and down events.
1029 *
1030 * @param prototype The prototype from which to create the injected events.
1031 * @param policyFlags The policy flags associated with the event.
1032 */
1033 private void sendActionDownAndUp(MotionEvent prototype, int policyFlags) {
Svetoslav Ganovbd206d12011-09-15 17:33:07 -07001034 // Tap with the pointer that last explored - we may have inactive pointers.
1035 final int pointerId = prototype.getPointerId(prototype.getActionIndex());
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001036 final int pointerIdBits = (1 << pointerId);
1037 sendMotionEvent(prototype, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags);
1038 sendMotionEvent(prototype, MotionEvent.ACTION_UP, pointerIdBits, policyFlags);
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001039 }
1040
1041 /**
Svetoslav Ganov91feae32011-05-19 18:16:31 -07001042 * Sends an event.
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001043 *
Svetoslav Ganov00f7b3f2011-06-07 19:24:10 -07001044 * @param prototype The prototype from which to create the injected events.
Svetoslav Ganov91feae32011-05-19 18:16:31 -07001045 * @param action The action of the event.
1046 * @param pointerIdBits The bits of the pointers to send.
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001047 * @param policyFlags The policy flags associated with the event.
1048 */
Svetoslav Ganov91feae32011-05-19 18:16:31 -07001049 private void sendMotionEvent(MotionEvent prototype, int action, int pointerIdBits,
1050 int policyFlags) {
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001051 prototype.setAction(action);
1052
1053 MotionEvent event = null;
1054 if (pointerIdBits == ALL_POINTER_ID_BITS) {
1055 event = prototype;
1056 } else {
1057 event = prototype.split(pointerIdBits);
1058 }
1059 if (action == MotionEvent.ACTION_DOWN) {
1060 event.setDownTime(event.getEventTime());
1061 } else {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001062 event.setDownTime(mInjectedPointerTracker.getLastInjectedDownEventTime());
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001063 }
1064
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001065 // If the user is long pressing but the long pressing pointer
1066 // was not exactly over the accessibility focused item we need
1067 // to remap the location of that pointer so the user does not
1068 // have to explicitly touch explore something to be able to
1069 // long press it, or even worse to avoid the user long pressing
1070 // on the wrong item since click and long press behave differently.
1071 if (mLongPressingPointerId >= 0) {
1072 final int remappedIndex = event.findPointerIndex(mLongPressingPointerId);
1073 final int pointerCount = event.getPointerCount();
1074 PointerProperties[] props = PointerProperties.createArray(pointerCount);
1075 PointerCoords[] coords = PointerCoords.createArray(pointerCount);
1076 for (int i = 0; i < pointerCount; i++) {
1077 event.getPointerProperties(i, props[i]);
1078 event.getPointerCoords(i, coords[i]);
1079 if (i == remappedIndex) {
1080 coords[i].x -= mLongPressingPointerDeltaX;
1081 coords[i].y -= mLongPressingPointerDeltaY;
1082 }
1083 }
1084 MotionEvent remapped = MotionEvent.obtain(event.getDownTime(),
1085 event.getEventTime(), event.getAction(), event.getPointerCount(),
1086 props, coords, event.getMetaState(), event.getButtonState(),
1087 1.0f, 1.0f, event.getDeviceId(), event.getEdgeFlags(),
1088 event.getSource(), event.getFlags());
1089 if (event != prototype) {
1090 event.recycle();
1091 }
1092 event = remapped;
1093 }
1094
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001095 if (DEBUG) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001096 Slog.d(LOG_TAG, "Injecting event: " + event + ", policyFlags=0x"
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001097 + Integer.toHexString(policyFlags));
1098 }
1099
1100 // Make sure that the user will see the event.
1101 policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -07001102 if (mNext != null) {
Svetoslav Ganov45af84a2012-10-01 16:25:08 -07001103 // TODO: For now pass null for the raw event since the touch
1104 // explorer is the last event transformation and it does
1105 // not care about the raw event.
1106 mNext.onMotionEvent(event, null, policyFlags);
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -07001107 }
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001108
Svetoslav Ganov42138042012-03-20 11:51:39 -07001109 mInjectedPointerTracker.onMotionEvent(event);
1110
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001111 if (event != prototype) {
1112 event.recycle();
1113 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001114 }
1115
1116 /**
1117 * Computes the action for an injected event based on a masked action
1118 * and a pointer index.
1119 *
1120 * @param actionMasked The masked action.
1121 * @param pointerIndex The index of the pointer which has changed.
1122 * @return The action to be used for injection.
1123 */
1124 private int computeInjectionAction(int actionMasked, int pointerIndex) {
1125 switch (actionMasked) {
1126 case MotionEvent.ACTION_DOWN:
1127 case MotionEvent.ACTION_POINTER_DOWN: {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001128 InjectedPointerTracker injectedTracker = mInjectedPointerTracker;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001129 // Compute the action based on how many down pointers are injected.
Svetoslav Ganov42138042012-03-20 11:51:39 -07001130 if (injectedTracker.getInjectedPointerDownCount() == 0) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001131 return MotionEvent.ACTION_DOWN;
1132 } else {
1133 return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
1134 | MotionEvent.ACTION_POINTER_DOWN;
1135 }
1136 }
1137 case MotionEvent.ACTION_POINTER_UP: {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001138 InjectedPointerTracker injectedTracker = mInjectedPointerTracker;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001139 // Compute the action based on how many down pointers are injected.
Svetoslav Ganov42138042012-03-20 11:51:39 -07001140 if (injectedTracker.getInjectedPointerDownCount() == 1) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001141 return MotionEvent.ACTION_UP;
1142 } else {
1143 return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
1144 | MotionEvent.ACTION_POINTER_UP;
1145 }
1146 }
1147 default:
1148 return actionMasked;
1149 }
1150 }
1151
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001152 private class DoubleTapDetector {
1153 private MotionEvent mDownEvent;
1154 private MotionEvent mFirstTapEvent;
1155
1156 public void onMotionEvent(MotionEvent event, int policyFlags) {
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -07001157 final int actionIndex = event.getActionIndex();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001158 final int action = event.getActionMasked();
1159 switch (action) {
1160 case MotionEvent.ACTION_DOWN:
1161 case MotionEvent.ACTION_POINTER_DOWN: {
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -07001162 if (mFirstTapEvent != null
1163 && !GestureUtils.isSamePointerContext(mFirstTapEvent, event)) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001164 clear();
1165 }
1166 mDownEvent = MotionEvent.obtain(event);
1167 } break;
1168 case MotionEvent.ACTION_UP:
1169 case MotionEvent.ACTION_POINTER_UP: {
1170 if (mDownEvent == null) {
1171 return;
1172 }
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -07001173 if (!GestureUtils.isSamePointerContext(mDownEvent, event)) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001174 clear();
1175 return;
1176 }
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -07001177 if (GestureUtils.isTap(mDownEvent, event, mTapTimeout, mTouchSlop,
1178 actionIndex)) {
1179 if (mFirstTapEvent == null || GestureUtils.isTimedOut(mFirstTapEvent,
1180 event, mDoubleTapTimeout)) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001181 mFirstTapEvent = MotionEvent.obtain(event);
1182 mDownEvent.recycle();
1183 mDownEvent = null;
1184 return;
1185 }
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -07001186 if (GestureUtils.isMultiTap(mFirstTapEvent, event, mDoubleTapTimeout,
1187 mDoubleTapSlop, actionIndex)) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001188 onDoubleTap(event, policyFlags);
1189 mFirstTapEvent.recycle();
1190 mFirstTapEvent = null;
1191 mDownEvent.recycle();
1192 mDownEvent = null;
1193 return;
1194 }
1195 mFirstTapEvent.recycle();
1196 mFirstTapEvent = null;
1197 } else {
1198 if (mFirstTapEvent != null) {
1199 mFirstTapEvent.recycle();
1200 mFirstTapEvent = null;
1201 }
1202 }
1203 mDownEvent.recycle();
1204 mDownEvent = null;
1205 } break;
1206 }
1207 }
1208
1209 public void onDoubleTap(MotionEvent secondTapUp, int policyFlags) {
1210 // This should never be called when more than two pointers are down.
1211 if (secondTapUp.getPointerCount() > 2) {
1212 return;
1213 }
1214
1215 // Remove pending event deliveries.
Svetoslav Ganov58d37b52012-09-18 12:04:19 -07001216 mSendHoverEnterDelayed.remove();
1217 mSendHoverExitDelayed.remove();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001218 mPerformLongPressDelayed.remove();
1219
Svetoslav Ganovf068fed2012-10-03 22:25:07 -07001220 if (mSendTouchExplorationEndDelayed.isPending()) {
1221 mSendTouchExplorationEndDelayed.forceSendAndRemove();
1222 }
1223 if (mSendTouchInteractionEndDelayed.isPending()) {
1224 mSendTouchInteractionEndDelayed.forceSendAndRemove();
1225 }
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001226
Svetoslav Ganov86783472012-06-06 21:12:20 -07001227 int clickLocationX;
1228 int clickLocationY;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001229
1230 final int pointerId = secondTapUp.getPointerId(secondTapUp.getActionIndex());
1231 final int pointerIndex = secondTapUp.findPointerIndex(pointerId);
Svetoslav Ganov86783472012-06-06 21:12:20 -07001232
Svetoslav Ganov5d043ce2012-06-14 10:30:00 -07001233 MotionEvent lastExploreEvent =
1234 mInjectedPointerTracker.getLastInjectedHoverEventForClick();
Svetoslav Ganov86783472012-06-06 21:12:20 -07001235 if (lastExploreEvent == null) {
1236 // No last touch explored event but there is accessibility focus in
1237 // the active window. We click in the middle of the focus bounds.
1238 Rect focusBounds = mTempRect;
1239 if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) {
1240 clickLocationX = focusBounds.centerX();
1241 clickLocationY = focusBounds.centerY();
1242 } else {
1243 // Out of luck - do nothing.
1244 return;
1245 }
1246 } else {
1247 // If the click is within the active window but not within the
1248 // accessibility focus bounds we click in the focus center.
1249 final int lastExplorePointerIndex = lastExploreEvent.getActionIndex();
1250 clickLocationX = (int) lastExploreEvent.getX(lastExplorePointerIndex);
1251 clickLocationY = (int) lastExploreEvent.getY(lastExplorePointerIndex);
1252 Rect activeWindowBounds = mTempRect;
Svetoslav Ganov385d9f22012-06-07 16:35:04 -07001253 if (mLastTouchedWindowId == mAms.getActiveWindowId()) {
1254 mAms.getActiveWindowBounds(activeWindowBounds);
1255 if (activeWindowBounds.contains(clickLocationX, clickLocationY)) {
1256 Rect focusBounds = mTempRect;
1257 if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) {
1258 if (!focusBounds.contains(clickLocationX, clickLocationY)) {
1259 clickLocationX = focusBounds.centerX();
1260 clickLocationY = focusBounds.centerY();
1261 }
Svetoslav Ganov86783472012-06-06 21:12:20 -07001262 }
1263 }
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001264 }
1265 }
1266
1267 // Do the click.
1268 PointerProperties[] properties = new PointerProperties[1];
1269 properties[0] = new PointerProperties();
1270 secondTapUp.getPointerProperties(pointerIndex, properties[0]);
1271 PointerCoords[] coords = new PointerCoords[1];
1272 coords[0] = new PointerCoords();
Svetoslav Ganov86783472012-06-06 21:12:20 -07001273 coords[0].x = clickLocationX;
1274 coords[0].y = clickLocationY;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001275 MotionEvent event = MotionEvent.obtain(secondTapUp.getDownTime(),
1276 secondTapUp.getEventTime(), MotionEvent.ACTION_DOWN, 1, properties,
1277 coords, 0, 0, 1.0f, 1.0f, secondTapUp.getDeviceId(), 0,
1278 secondTapUp.getSource(), secondTapUp.getFlags());
1279 sendActionDownAndUp(event, policyFlags);
1280 event.recycle();
1281 }
1282
1283 public void clear() {
1284 if (mDownEvent != null) {
1285 mDownEvent.recycle();
1286 mDownEvent = null;
1287 }
1288 if (mFirstTapEvent != null) {
1289 mFirstTapEvent.recycle();
1290 mFirstTapEvent = null;
1291 }
1292 }
1293
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001294 public boolean firstTapDetected() {
1295 return mFirstTapEvent != null
1296 && SystemClock.uptimeMillis() - mFirstTapEvent.getEventTime() < mDoubleTapTimeout;
1297 }
1298 }
1299
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001300 /**
1301 * Determines whether a two pointer gesture is a dragging one.
1302 *
1303 * @param event The event with the pointer data.
1304 * @return True if the gesture is a dragging one.
1305 */
1306 private boolean isDraggingGesture(MotionEvent event) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001307 ReceivedPointerTracker receivedTracker = mReceivedPointerTracker;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001308 int[] pointerIds = mTempPointerIds;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001309 receivedTracker.populateActivePointerIds(pointerIds);
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001310
1311 final int firstPtrIndex = event.findPointerIndex(pointerIds[0]);
1312 final int secondPtrIndex = event.findPointerIndex(pointerIds[1]);
1313
1314 final float firstPtrX = event.getX(firstPtrIndex);
1315 final float firstPtrY = event.getY(firstPtrIndex);
1316 final float secondPtrX = event.getX(secondPtrIndex);
1317 final float secondPtrY = event.getY(secondPtrIndex);
1318
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -07001319 final float firstPtrDownX = receivedTracker.getReceivedPointerDownX(firstPtrIndex);
1320 final float firstPtrDownY = receivedTracker.getReceivedPointerDownY(firstPtrIndex);
1321 final float secondPtrDownX = receivedTracker.getReceivedPointerDownX(secondPtrIndex);
1322 final float secondPtrDownY = receivedTracker.getReceivedPointerDownY(secondPtrIndex);
Svetoslav Ganovf8044202011-08-26 20:33:33 -07001323
Svetoslav Ganov1cf70bb2012-08-06 10:53:34 -07001324 return GestureUtils.isDraggingGesture(firstPtrDownX, firstPtrDownY, secondPtrDownX,
1325 secondPtrDownY, firstPtrX, firstPtrY, secondPtrX, secondPtrY,
1326 MAX_DRAGGING_ANGLE_COS);
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001327 }
1328
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001329 /**
Svetoslav Ganov51cccf02011-06-27 12:00:54 -07001330 * Gets the symbolic name of a state.
1331 *
1332 * @param state A state.
1333 * @return The state symbolic name.
1334 */
1335 private static String getStateSymbolicName(int state) {
1336 switch (state) {
1337 case STATE_TOUCH_EXPLORING:
1338 return "STATE_TOUCH_EXPLORING";
1339 case STATE_DRAGGING:
1340 return "STATE_DRAGGING";
1341 case STATE_DELEGATING:
1342 return "STATE_DELEGATING";
Svetoslav Ganov42138042012-03-20 11:51:39 -07001343 case STATE_GESTURE_DETECTING:
1344 return "STATE_GESTURE_DETECTING";
Svetoslav Ganov51cccf02011-06-27 12:00:54 -07001345 default:
1346 throw new IllegalArgumentException("Unknown state: " + state);
1347 }
1348 }
1349
1350 /**
Svetoslav Ganov42138042012-03-20 11:51:39 -07001351 * @return The number of non injected active pointers.
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001352 */
Svetoslav Ganov42138042012-03-20 11:51:39 -07001353 private int getNotInjectedActivePointerCount(ReceivedPointerTracker receivedTracker,
1354 InjectedPointerTracker injectedTracker) {
1355 final int pointerState = receivedTracker.getActivePointers()
1356 & ~injectedTracker.getInjectedPointersDown();
1357 return Integer.bitCount(pointerState);
1358 }
1359
1360 /**
Svetoslav Ganov95068e52012-06-13 21:01:51 -07001361 * Class for delayed exiting from gesture detecting mode.
1362 */
1363 private final class ExitGestureDetectionModeDelayed implements Runnable {
1364
1365 public void post() {
1366 mHandler.postDelayed(this, EXIT_GESTURE_DETECTION_TIMEOUT);
1367 }
1368
1369 public void remove() {
1370 mHandler.removeCallbacks(this);
1371 }
1372
1373 @Override
1374 public void run() {
Svetoslav Ganovaed4b6f2012-09-28 10:06:18 -07001375 // Announce the end of gesture recognition.
1376 sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END);
1377 // Clearing puts is in touch exploration state with a finger already
1378 // down, so announce the transition to exploration state.
1379 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
Svetoslav Ganov95068e52012-06-13 21:01:51 -07001380 clear();
1381 }
1382 }
1383
1384 /**
Svetoslav Ganov42138042012-03-20 11:51:39 -07001385 * Class for delayed sending of long press.
1386 */
1387 private final class PerformLongPressDelayed implements Runnable {
1388 private MotionEvent mEvent;
1389 private int mPolicyFlags;
1390
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001391 public void post(MotionEvent prototype, int policyFlags) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001392 mEvent = MotionEvent.obtain(prototype);
1393 mPolicyFlags = policyFlags;
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001394 mHandler.postDelayed(this, ViewConfiguration.getLongPressTimeout());
Svetoslav Ganov42138042012-03-20 11:51:39 -07001395 }
1396
1397 public void remove() {
Svetoslav Ganov77276b62012-09-14 10:23:00 -07001398 if (isPending()) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001399 mHandler.removeCallbacks(this);
1400 clear();
1401 }
1402 }
1403
Svetoslav Ganov77276b62012-09-14 10:23:00 -07001404 public boolean isPending() {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001405 return (mEvent != null);
1406 }
1407
1408 @Override
1409 public void run() {
Svetoslav Ganovebac1b72012-06-02 16:26:44 -07001410 // Active pointers should not be zero when running this command.
1411 if (mReceivedPointerTracker.getActivePointerCount() == 0) {
1412 return;
1413 }
1414
Svetoslav Ganov86783472012-06-06 21:12:20 -07001415 int clickLocationX;
1416 int clickLocationY;
Svetoslav Ganov238099c2012-06-01 13:52:54 -07001417
1418 final int pointerId = mEvent.getPointerId(mEvent.getActionIndex());
1419 final int pointerIndex = mEvent.findPointerIndex(pointerId);
Svetoslav Ganov86783472012-06-06 21:12:20 -07001420
Svetoslav Ganov5d043ce2012-06-14 10:30:00 -07001421 MotionEvent lastExploreEvent =
1422 mInjectedPointerTracker.getLastInjectedHoverEventForClick();
Svetoslav Ganov86783472012-06-06 21:12:20 -07001423 if (lastExploreEvent == null) {
1424 // No last touch explored event but there is accessibility focus in
1425 // the active window. We click in the middle of the focus bounds.
1426 Rect focusBounds = mTempRect;
1427 if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) {
1428 clickLocationX = focusBounds.centerX();
1429 clickLocationY = focusBounds.centerY();
1430 } else {
1431 // Out of luck - do nothing.
1432 return;
1433 }
1434 } else {
1435 // If the click is within the active window but not within the
1436 // accessibility focus bounds we click in the focus center.
1437 final int lastExplorePointerIndex = lastExploreEvent.getActionIndex();
1438 clickLocationX = (int) lastExploreEvent.getX(lastExplorePointerIndex);
1439 clickLocationY = (int) lastExploreEvent.getY(lastExplorePointerIndex);
1440 Rect activeWindowBounds = mTempRect;
Svetoslav Ganov385d9f22012-06-07 16:35:04 -07001441 if (mLastTouchedWindowId == mAms.getActiveWindowId()) {
1442 mAms.getActiveWindowBounds(activeWindowBounds);
1443 if (activeWindowBounds.contains(clickLocationX, clickLocationY)) {
1444 Rect focusBounds = mTempRect;
1445 if (mAms.getAccessibilityFocusBoundsInActiveWindow(focusBounds)) {
1446 if (!focusBounds.contains(clickLocationX, clickLocationY)) {
1447 clickLocationX = focusBounds.centerX();
1448 clickLocationY = focusBounds.centerY();
1449 }
Svetoslav Ganov86783472012-06-06 21:12:20 -07001450 }
1451 }
Svetoslav Ganov238099c2012-06-01 13:52:54 -07001452 }
1453 }
1454
Svetoslav Ganov86783472012-06-06 21:12:20 -07001455 mLongPressingPointerId = pointerId;
1456 mLongPressingPointerDeltaX = (int) mEvent.getX(pointerIndex) - clickLocationX;
1457 mLongPressingPointerDeltaY = (int) mEvent.getY(pointerIndex) - clickLocationY;
Svetoslav Ganov238099c2012-06-01 13:52:54 -07001458
Svetoslav Ganovf068fed2012-10-03 22:25:07 -07001459 sendHoverExitAndTouchExplorationGestureEndIfNeeded(mPolicyFlags);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001460
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001461 mCurrentState = STATE_DELEGATING;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001462 sendDownForAllActiveNotInjectedPointers(mEvent, mPolicyFlags);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001463 clear();
1464 }
1465
1466 private void clear() {
Svetoslav Ganov77276b62012-09-14 10:23:00 -07001467 if (!isPending()) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001468 return;
1469 }
1470 mEvent.recycle();
1471 mEvent = null;
1472 mPolicyFlags = 0;
1473 }
1474 }
1475
1476 /**
1477 * Class for delayed sending of hover events.
1478 */
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001479 class SendHoverDelayed implements Runnable {
1480 private final String LOG_TAG_SEND_HOVER_DELAYED = SendHoverDelayed.class.getName();
1481
1482 private final int mHoverAction;
1483 private final boolean mGestureStarted;
1484
1485 private MotionEvent mPrototype;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001486 private int mPointerIdBits;
1487 private int mPolicyFlags;
1488
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001489 public SendHoverDelayed(int hoverAction, boolean gestureStarted) {
1490 mHoverAction = hoverAction;
1491 mGestureStarted = gestureStarted;
1492 }
1493
Svetoslav Ganov77276b62012-09-14 10:23:00 -07001494 public void post(MotionEvent prototype, boolean touchExplorationInProgress,
1495 int pointerIdBits, int policyFlags) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001496 remove();
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001497 mPrototype = MotionEvent.obtain(prototype);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001498 mPointerIdBits = pointerIdBits;
1499 mPolicyFlags = policyFlags;
Svetoslav Ganove47957a2012-06-05 14:46:50 -07001500 mHandler.postDelayed(this, mDetermineUserIntentTimeout);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001501 }
1502
1503 public float getX() {
1504 if (isPending()) {
1505 return mPrototype.getX();
1506 }
1507 return 0;
1508 }
1509
1510 public float getY() {
1511 if (isPending()) {
1512 return mPrototype.getY();
1513 }
1514 return 0;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001515 }
1516
1517 public void remove() {
1518 mHandler.removeCallbacks(this);
1519 clear();
1520 }
1521
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001522 private boolean isPending() {
1523 return (mPrototype != null);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001524 }
1525
1526 private void clear() {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001527 if (!isPending()) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001528 return;
1529 }
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001530 mPrototype.recycle();
1531 mPrototype = null;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001532 mPointerIdBits = -1;
1533 mPolicyFlags = 0;
1534 }
1535
1536 public void forceSendAndRemove() {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001537 if (isPending()) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001538 run();
1539 remove();
1540 }
1541 }
1542
1543 public void run() {
1544 if (DEBUG) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001545 Slog.d(LOG_TAG_SEND_HOVER_DELAYED, "Injecting motion event: "
1546 + MotionEvent.actionToString(mHoverAction));
1547 Slog.d(LOG_TAG_SEND_HOVER_DELAYED, mGestureStarted ?
1548 "touchExplorationGestureStarted" : "touchExplorationGestureEnded");
Svetoslav Ganov42138042012-03-20 11:51:39 -07001549 }
Svetoslav Ganovf068fed2012-10-03 22:25:07 -07001550 if (mGestureStarted) {
1551 sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START);
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001552 } else {
Svetoslav Ganovf068fed2012-10-03 22:25:07 -07001553 if (!mSendTouchExplorationEndDelayed.isPending()) {
1554 mSendTouchExplorationEndDelayed.post();
1555 }
1556 if (mSendTouchInteractionEndDelayed.isPending()) {
1557 mSendTouchInteractionEndDelayed.remove();
1558 mSendTouchInteractionEndDelayed.post();
Svetoslav Ganov77276b62012-09-14 10:23:00 -07001559 }
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001560 }
1561 sendMotionEvent(mPrototype, mHoverAction, mPointerIdBits, mPolicyFlags);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001562 clear();
1563 }
1564 }
1565
Svetoslav Ganovf068fed2012-10-03 22:25:07 -07001566 private class SendAccessibilityEventDelayed implements Runnable {
1567 private final int mEventType;
1568 private final int mDelay;
1569
1570 public SendAccessibilityEventDelayed(int eventType, int delay) {
1571 mEventType = eventType;
1572 mDelay = delay;
1573 }
Svetoslav Ganovfe304b82012-09-28 11:12:34 -07001574
1575 public void remove() {
1576 mHandler.removeCallbacks(this);
1577 }
1578
1579 public void post() {
Svetoslav Ganovf068fed2012-10-03 22:25:07 -07001580 mHandler.postDelayed(this, mDelay);
Svetoslav Ganovfe304b82012-09-28 11:12:34 -07001581 }
1582
1583 public boolean isPending() {
1584 return mHandler.hasCallbacks(this);
1585 }
1586
1587 public void forceSendAndRemove() {
1588 if (isPending()) {
1589 run();
1590 remove();
1591 }
1592 }
1593
1594 @Override
1595 public void run() {
Svetoslav Ganovf068fed2012-10-03 22:25:07 -07001596 sendAccessibilityEvent(mEventType);
Svetoslav Ganovfe304b82012-09-28 11:12:34 -07001597 }
1598 }
1599
Svetoslav Ganov42138042012-03-20 11:51:39 -07001600 @Override
1601 public String toString() {
1602 return LOG_TAG;
1603 }
1604
1605 class InjectedPointerTracker {
1606 private static final String LOG_TAG_INJECTED_POINTER_TRACKER = "InjectedPointerTracker";
1607
1608 // Keep track of which pointers sent to the system are down.
1609 private int mInjectedPointersDown;
1610
1611 // The time of the last injected down.
1612 private long mLastInjectedDownEventTime;
1613
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001614 // The last injected hover event.
1615 private MotionEvent mLastInjectedHoverEvent;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001616
Svetoslav Ganov5d043ce2012-06-14 10:30:00 -07001617 // The last injected hover event used for performing clicks.
1618 private MotionEvent mLastInjectedHoverEventForClick;
1619
Svetoslav Ganov42138042012-03-20 11:51:39 -07001620 /**
1621 * Processes an injected {@link MotionEvent} event.
1622 *
1623 * @param event The event to process.
1624 */
1625 public void onMotionEvent(MotionEvent event) {
1626 final int action = event.getActionMasked();
1627 switch (action) {
1628 case MotionEvent.ACTION_DOWN:
1629 case MotionEvent.ACTION_POINTER_DOWN: {
1630 final int pointerId = event.getPointerId(event.getActionIndex());
1631 final int pointerFlag = (1 << pointerId);
1632 mInjectedPointersDown |= pointerFlag;
1633 mLastInjectedDownEventTime = event.getDownTime();
1634 } break;
1635 case MotionEvent.ACTION_UP:
1636 case MotionEvent.ACTION_POINTER_UP: {
1637 final int pointerId = event.getPointerId(event.getActionIndex());
1638 final int pointerFlag = (1 << pointerId);
1639 mInjectedPointersDown &= ~pointerFlag;
1640 if (mInjectedPointersDown == 0) {
1641 mLastInjectedDownEventTime = 0;
1642 }
1643 } break;
1644 case MotionEvent.ACTION_HOVER_ENTER:
1645 case MotionEvent.ACTION_HOVER_MOVE:
1646 case MotionEvent.ACTION_HOVER_EXIT: {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001647 if (mLastInjectedHoverEvent != null) {
1648 mLastInjectedHoverEvent.recycle();
1649 }
1650 mLastInjectedHoverEvent = MotionEvent.obtain(event);
Svetoslav Ganov5d043ce2012-06-14 10:30:00 -07001651 if (mLastInjectedHoverEventForClick != null) {
1652 mLastInjectedHoverEventForClick.recycle();
1653 }
1654 mLastInjectedHoverEventForClick = MotionEvent.obtain(event);
Svetoslav Ganov42138042012-03-20 11:51:39 -07001655 } break;
1656 }
1657 if (DEBUG) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001658 Slog.i(LOG_TAG_INJECTED_POINTER_TRACKER, "Injected pointer:\n" + toString());
Svetoslav Ganov42138042012-03-20 11:51:39 -07001659 }
1660 }
1661
1662 /**
1663 * Clears the internals state.
1664 */
1665 public void clear() {
1666 mInjectedPointersDown = 0;
1667 }
1668
1669 /**
1670 * @return The time of the last injected down event.
1671 */
1672 public long getLastInjectedDownEventTime() {
1673 return mLastInjectedDownEventTime;
1674 }
1675
1676 /**
1677 * @return The number of down pointers injected to the view hierarchy.
1678 */
1679 public int getInjectedPointerDownCount() {
1680 return Integer.bitCount(mInjectedPointersDown);
1681 }
1682
1683 /**
1684 * @return The bits of the injected pointers that are down.
1685 */
1686 public int getInjectedPointersDown() {
1687 return mInjectedPointersDown;
1688 }
1689
1690 /**
1691 * Whether an injected pointer is down.
1692 *
1693 * @param pointerId The unique pointer id.
1694 * @return True if the pointer is down.
1695 */
1696 public boolean isInjectedPointerDown(int pointerId) {
1697 final int pointerFlag = (1 << pointerId);
1698 return (mInjectedPointersDown & pointerFlag) != 0;
1699 }
1700
1701 /**
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001702 * @return The the last injected hover event.
Svetoslav Ganov42138042012-03-20 11:51:39 -07001703 */
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001704 public MotionEvent getLastInjectedHoverEvent() {
1705 return mLastInjectedHoverEvent;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001706 }
1707
Svetoslav Ganov5d043ce2012-06-14 10:30:00 -07001708 /**
1709 * @return The the last injected hover event.
1710 */
1711 public MotionEvent getLastInjectedHoverEventForClick() {
1712 return mLastInjectedHoverEventForClick;
1713 }
1714
Svetoslav Ganov42138042012-03-20 11:51:39 -07001715 @Override
1716 public String toString() {
1717 StringBuilder builder = new StringBuilder();
1718 builder.append("=========================");
1719 builder.append("\nDown pointers #");
1720 builder.append(Integer.bitCount(mInjectedPointersDown));
1721 builder.append(" [ ");
1722 for (int i = 0; i < MAX_POINTER_COUNT; i++) {
1723 if ((mInjectedPointersDown & i) != 0) {
1724 builder.append(i);
1725 builder.append(" ");
1726 }
1727 }
1728 builder.append("]");
1729 builder.append("\n=========================");
1730 return builder.toString();
1731 }
1732 }
1733
1734 class ReceivedPointerTracker {
1735 private static final String LOG_TAG_RECEIVED_POINTER_TRACKER = "ReceivedPointerTracker";
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001736
1737 // The coefficient by which to multiply
1738 // ViewConfiguration.#getScaledTouchSlop()
1739 // to compute #mThresholdActivePointer.
1740 private static final int COEFFICIENT_ACTIVE_POINTER = 2;
1741
1742 // Pointers that moved less than mThresholdActivePointer
1743 // are considered active i.e. are ignored.
1744 private final double mThresholdActivePointer;
1745
1746 // Keep track of where and when a pointer went down.
1747 private final float[] mReceivedPointerDownX = new float[MAX_POINTER_COUNT];
1748 private final float[] mReceivedPointerDownY = new float[MAX_POINTER_COUNT];
1749 private final long[] mReceivedPointerDownTime = new long[MAX_POINTER_COUNT];
1750
1751 // Which pointers are down.
1752 private int mReceivedPointersDown;
1753
1754 // Which down pointers are active.
1755 private int mActivePointers;
1756
1757 // Primary active pointer which is either the first that went down
1758 // or if it goes up the next active that most recently went down.
1759 private int mPrimaryActivePointerId;
1760
1761 // Flag indicating that there is at least one active pointer moving.
1762 private boolean mHasMovingActivePointer;
1763
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001764 // Keep track of the last up pointer data.
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001765 private long mLastReceivedUpPointerDownTime;
1766 private int mLastReceivedUpPointerId;
1767 private boolean mLastReceivedUpPointerActive;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001768 private float mLastReceivedUpPointerDownX;
1769 private float mLastReceivedUpPointerDownY;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001770
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001771 private MotionEvent mLastReceivedEvent;
1772
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001773 /**
1774 * Creates a new instance.
1775 *
1776 * @param context Context for looking up resources.
1777 */
Svetoslav Ganov42138042012-03-20 11:51:39 -07001778 public ReceivedPointerTracker(Context context) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001779 mThresholdActivePointer =
Svetoslav Ganov76c0dd42012-09-24 19:16:16 -07001780 ViewConfiguration.get(context).getScaledTouchSlop() * COEFFICIENT_ACTIVE_POINTER;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001781 }
1782
1783 /**
1784 * Clears the internals state.
1785 */
1786 public void clear() {
1787 Arrays.fill(mReceivedPointerDownX, 0);
1788 Arrays.fill(mReceivedPointerDownY, 0);
1789 Arrays.fill(mReceivedPointerDownTime, 0);
1790 mReceivedPointersDown = 0;
1791 mActivePointers = 0;
1792 mPrimaryActivePointerId = 0;
1793 mHasMovingActivePointer = false;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001794 mLastReceivedUpPointerDownTime = 0;
1795 mLastReceivedUpPointerId = 0;
1796 mLastReceivedUpPointerActive = false;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001797 mLastReceivedUpPointerDownX = 0;
1798 mLastReceivedUpPointerDownY = 0;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001799 }
1800
1801 /**
1802 * Processes a received {@link MotionEvent} event.
1803 *
1804 * @param event The event to process.
1805 */
Svetoslav Ganov42138042012-03-20 11:51:39 -07001806 public void onMotionEvent(MotionEvent event) {
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001807 if (mLastReceivedEvent != null) {
1808 mLastReceivedEvent.recycle();
1809 }
1810 mLastReceivedEvent = MotionEvent.obtain(event);
1811
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001812 final int action = event.getActionMasked();
1813 switch (action) {
1814 case MotionEvent.ACTION_DOWN: {
Svetoslav Ganov00f7b3f2011-06-07 19:24:10 -07001815 handleReceivedPointerDown(event.getActionIndex(), event);
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001816 } break;
1817 case MotionEvent.ACTION_POINTER_DOWN: {
1818 handleReceivedPointerDown(event.getActionIndex(), event);
1819 } break;
1820 case MotionEvent.ACTION_MOVE: {
1821 handleReceivedPointerMove(event);
1822 } break;
1823 case MotionEvent.ACTION_UP: {
Svetoslav Ganov00f7b3f2011-06-07 19:24:10 -07001824 handleReceivedPointerUp(event.getActionIndex(), event);
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001825 } break;
1826 case MotionEvent.ACTION_POINTER_UP: {
1827 handleReceivedPointerUp(event.getActionIndex(), event);
1828 } break;
1829 }
1830 if (DEBUG) {
Svetoslav Ganov42138042012-03-20 11:51:39 -07001831 Slog.i(LOG_TAG_RECEIVED_POINTER_TRACKER, "Received pointer: " + toString());
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001832 }
1833 }
1834
1835 /**
Svetoslav Ganove15ccb92012-05-16 15:48:55 -07001836 * @return The last received event.
1837 */
1838 public MotionEvent getLastReceivedEvent() {
1839 return mLastReceivedEvent;
1840 }
1841
1842 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001843 * @return The number of received pointers that are down.
1844 */
1845 public int getReceivedPointerDownCount() {
1846 return Integer.bitCount(mReceivedPointersDown);
1847 }
1848
1849 /**
Svetoslav Ganov42138042012-03-20 11:51:39 -07001850 * @return The bits of the pointers that are active.
1851 */
1852 public int getActivePointers() {
1853 return mActivePointers;
1854 }
1855
1856 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001857 * @return The number of down input pointers that are active.
1858 */
1859 public int getActivePointerCount() {
1860 return Integer.bitCount(mActivePointers);
1861 }
1862
1863 /**
1864 * Whether an received pointer is down.
1865 *
1866 * @param pointerId The unique pointer id.
1867 * @return True if the pointer is down.
1868 */
1869 public boolean isReceivedPointerDown(int pointerId) {
1870 final int pointerFlag = (1 << pointerId);
1871 return (mReceivedPointersDown & pointerFlag) != 0;
1872 }
1873
1874 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001875 * Whether an input pointer is active.
1876 *
1877 * @param pointerId The unique pointer id.
1878 * @return True if the pointer is active.
1879 */
1880 public boolean isActivePointer(int pointerId) {
1881 final int pointerFlag = (1 << pointerId);
1882 return (mActivePointers & pointerFlag) != 0;
1883 }
1884
1885 /**
1886 * @param pointerId The unique pointer id.
1887 * @return The X coordinate where the pointer went down.
1888 */
1889 public float getReceivedPointerDownX(int pointerId) {
1890 return mReceivedPointerDownX[pointerId];
1891 }
1892
1893 /**
1894 * @param pointerId The unique pointer id.
1895 * @return The Y coordinate where the pointer went down.
1896 */
1897 public float getReceivedPointerDownY(int pointerId) {
1898 return mReceivedPointerDownY[pointerId];
1899 }
1900
1901 /**
1902 * @param pointerId The unique pointer id.
1903 * @return The time when the pointer went down.
1904 */
1905 public long getReceivedPointerDownTime(int pointerId) {
1906 return mReceivedPointerDownTime[pointerId];
1907 }
1908
1909 /**
1910 * @return The id of the primary pointer.
1911 */
1912 public int getPrimaryActivePointerId() {
1913 if (mPrimaryActivePointerId == INVALID_POINTER_ID) {
1914 mPrimaryActivePointerId = findPrimaryActivePointer();
1915 }
1916 return mPrimaryActivePointerId;
1917 }
1918
1919 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001920 * @return The time when the last up received pointer went down.
1921 */
1922 public long getLastReceivedUpPointerDownTime() {
1923 return mLastReceivedUpPointerDownTime;
1924 }
1925
1926 /**
1927 * @return The id of the last received pointer that went up.
1928 */
1929 public int getLastReceivedUpPointerId() {
1930 return mLastReceivedUpPointerId;
1931 }
1932
Svetoslav Ganov42138042012-03-20 11:51:39 -07001933
1934 /**
1935 * @return The down X of the last received pointer that went up.
1936 */
1937 public float getLastReceivedUpPointerDownX() {
1938 return mLastReceivedUpPointerDownX;
1939 }
1940
1941 /**
1942 * @return The down Y of the last received pointer that went up.
1943 */
1944 public float getLastReceivedUpPointerDownY() {
1945 return mLastReceivedUpPointerDownY;
1946 }
1947
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001948 /**
1949 * @return Whether the last received pointer that went up was active.
1950 */
1951 public boolean wasLastReceivedUpPointerActive() {
1952 return mLastReceivedUpPointerActive;
1953 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001954 /**
1955 * Populates the active pointer IDs to the given array.
1956 * <p>
1957 * Note: The client is responsible for providing large enough array.
1958 *
1959 * @param outPointerIds The array to which to write the active pointers.
1960 */
1961 public void populateActivePointerIds(int[] outPointerIds) {
1962 int index = 0;
1963 for (int idBits = mActivePointers; idBits != 0; ) {
1964 final int id = Integer.numberOfTrailingZeros(idBits);
1965 idBits &= ~(1 << id);
1966 outPointerIds[index] = id;
1967 index++;
1968 }
1969 }
1970
1971 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001972 * @param pointerId The unique pointer id.
1973 * @return Whether the pointer is active or was the last active than went up.
1974 */
Svetoslav Ganov42138042012-03-20 11:51:39 -07001975 public boolean isActiveOrWasLastActiveUpPointer(int pointerId) {
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001976 return (isActivePointer(pointerId)
1977 || (mLastReceivedUpPointerId == pointerId
1978 && mLastReceivedUpPointerActive));
1979 }
1980
1981 /**
1982 * Handles a received pointer down event.
1983 *
1984 * @param pointerIndex The index of the pointer that has changed.
1985 * @param event The event to be handled.
1986 */
1987 private void handleReceivedPointerDown(int pointerIndex, MotionEvent event) {
1988 final int pointerId = event.getPointerId(pointerIndex);
1989 final int pointerFlag = (1 << pointerId);
1990
1991 mLastReceivedUpPointerId = 0;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001992 mLastReceivedUpPointerDownTime = 0;
1993 mLastReceivedUpPointerActive = false;
Svetoslav Ganov42138042012-03-20 11:51:39 -07001994 mLastReceivedUpPointerDownX = 0;
1995 mLastReceivedUpPointerDownX = 0;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07001996
1997 mReceivedPointersDown |= pointerFlag;
1998 mReceivedPointerDownX[pointerId] = event.getX(pointerIndex);
1999 mReceivedPointerDownY[pointerId] = event.getY(pointerIndex);
2000 mReceivedPointerDownTime[pointerId] = event.getEventTime();
2001
2002 if (!mHasMovingActivePointer) {
2003 // If still no moving active pointers every
2004 // down pointer is the only active one.
2005 mActivePointers = pointerFlag;
2006 mPrimaryActivePointerId = pointerId;
2007 } else {
2008 // If at least one moving active pointer every
2009 // subsequent down pointer is active.
2010 mActivePointers |= pointerFlag;
2011 }
2012 }
2013
2014 /**
2015 * Handles a received pointer move event.
2016 *
2017 * @param event The event to be handled.
2018 */
2019 private void handleReceivedPointerMove(MotionEvent event) {
2020 detectActivePointers(event);
2021 }
2022
2023 /**
2024 * Handles a received pointer up event.
2025 *
2026 * @param pointerIndex The index of the pointer that has changed.
2027 * @param event The event to be handled.
2028 */
2029 private void handleReceivedPointerUp(int pointerIndex, MotionEvent event) {
2030 final int pointerId = event.getPointerId(pointerIndex);
2031 final int pointerFlag = (1 << pointerId);
2032
2033 mLastReceivedUpPointerId = pointerId;
Svetoslav Ganov736c2752011-04-22 18:30:36 -07002034 mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId);
2035 mLastReceivedUpPointerActive = isActivePointer(pointerId);
Svetoslav Ganov42138042012-03-20 11:51:39 -07002036 mLastReceivedUpPointerDownX = mReceivedPointerDownX[pointerId];
2037 mLastReceivedUpPointerDownY = mReceivedPointerDownY[pointerId];
Svetoslav Ganov736c2752011-04-22 18:30:36 -07002038
2039 mReceivedPointersDown &= ~pointerFlag;
2040 mActivePointers &= ~pointerFlag;
2041 mReceivedPointerDownX[pointerId] = 0;
2042 mReceivedPointerDownY[pointerId] = 0;
2043 mReceivedPointerDownTime[pointerId] = 0;
2044
2045 if (mActivePointers == 0) {
2046 mHasMovingActivePointer = false;
2047 }
2048 if (mPrimaryActivePointerId == pointerId) {
2049 mPrimaryActivePointerId = INVALID_POINTER_ID;
2050 }
2051 }
2052
2053 /**
Svetoslav Ganov736c2752011-04-22 18:30:36 -07002054 * Detects the active pointers in an event.
2055 *
2056 * @param event The event to examine.
2057 */
2058 private void detectActivePointers(MotionEvent event) {
2059 for (int i = 0, count = event.getPointerCount(); i < count; i++) {
2060 final int pointerId = event.getPointerId(i);
2061 if (mHasMovingActivePointer) {
2062 // If already active => nothing to do.
2063 if (isActivePointer(pointerId)) {
2064 continue;
2065 }
2066 }
2067 // Active pointers are ones that moved more than a given threshold.
2068 final float pointerDeltaMove = computePointerDeltaMove(i, event);
2069 if (pointerDeltaMove > mThresholdActivePointer) {
2070 final int pointerFlag = (1 << pointerId);
2071 mActivePointers |= pointerFlag;
2072 mHasMovingActivePointer = true;
2073 }
2074 }
2075 }
2076
2077 /**
2078 * @return The primary active pointer.
2079 */
2080 private int findPrimaryActivePointer() {
2081 int primaryActivePointerId = INVALID_POINTER_ID;
2082 long minDownTime = Long.MAX_VALUE;
2083 // Find the active pointer that went down first.
2084 for (int i = 0, count = mReceivedPointerDownTime.length; i < count; i++) {
2085 if (isActivePointer(i)) {
2086 final long downPointerTime = mReceivedPointerDownTime[i];
2087 if (downPointerTime < minDownTime) {
2088 minDownTime = downPointerTime;
2089 primaryActivePointerId = i;
2090 }
2091 }
2092 }
2093 return primaryActivePointerId;
2094 }
2095
2096 /**
2097 * Computes the move for a given action pointer index since the
2098 * corresponding pointer went down.
2099 *
2100 * @param pointerIndex The action pointer index.
2101 * @param event The event to examine.
2102 * @return The distance the pointer has moved.
2103 */
2104 private float computePointerDeltaMove(int pointerIndex, MotionEvent event) {
2105 final int pointerId = event.getPointerId(pointerIndex);
2106 final float deltaX = event.getX(pointerIndex) - mReceivedPointerDownX[pointerId];
2107 final float deltaY = event.getY(pointerIndex) - mReceivedPointerDownY[pointerId];
2108 return (float) Math.hypot(deltaX, deltaY);
2109 }
2110
2111 @Override
2112 public String toString() {
2113 StringBuilder builder = new StringBuilder();
2114 builder.append("=========================");
2115 builder.append("\nDown pointers #");
2116 builder.append(getReceivedPointerDownCount());
2117 builder.append(" [ ");
2118 for (int i = 0; i < MAX_POINTER_COUNT; i++) {
2119 if (isReceivedPointerDown(i)) {
2120 builder.append(i);
2121 builder.append(" ");
2122 }
2123 }
2124 builder.append("]");
2125 builder.append("\nActive pointers #");
2126 builder.append(getActivePointerCount());
2127 builder.append(" [ ");
2128 for (int i = 0; i < MAX_POINTER_COUNT; i++) {
2129 if (isActivePointer(i)) {
2130 builder.append(i);
2131 builder.append(" ");
2132 }
2133 }
2134 builder.append("]");
2135 builder.append("\nPrimary active pointer id [ ");
2136 builder.append(getPrimaryActivePointerId());
2137 builder.append(" ]");
2138 builder.append("\n=========================");
2139 return builder.toString();
2140 }
2141 }
Svetoslav Ganov736c2752011-04-22 18:30:36 -07002142}