1 /*
2  * Copyright (C) 2019 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 
17 package com.android.server.accessibility.gestures;
18 
19 import static android.view.MotionEvent.INVALID_POINTER_ID;
20 
21 import static com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
22 
23 import android.annotation.IntDef;
24 import android.util.Slog;
25 import android.view.MotionEvent;
26 import android.view.accessibility.AccessibilityEvent;
27 
28 /**
29  * This class describes the state of the touch explorer as well as the state of received and
30  * injected pointers. This data is accessed both for purposes of touch exploration and gesture
31  * dispatch.
32  */
33 public class TouchState {
34     private static final String LOG_TAG = "TouchState";
35     // Pointer-related constants
36     // This constant captures the current implementation detail that
37     // pointer IDs are between 0 and 31 inclusive (subject to change).
38     // (See MAX_POINTER_ID in frameworks/base/include/ui/Input.h)
39     static final int MAX_POINTER_COUNT = 32;
40     // Constant referring to the ids bits of all pointers.
41     public static final int ALL_POINTER_ID_BITS = 0xFFFFFFFF;
42 
43     // States that the touch explorer can be in.
44     // In the clear state the user is not touching the screen.
45     public static final int STATE_CLEAR = 0;
46     // The user is touching the screen and we are trying to figure out their intent.
47     // This state gets its name from the TYPE_TOUCH_INTERACTION start and end accessibility events.
48     public static final int STATE_TOUCH_INTERACTING = 1;
49     // The user is explicitly exploring the screen.
50     public static final int STATE_TOUCH_EXPLORING = 2;
51     // the user is dragging with two fingers.
52     public static final int STATE_DRAGGING = 3;
53     // The user is performing some other two finger gesture which we pass through to the view
54     // hierarchy as a one-finger gesture e.g. two-finger scrolling.
55     public static final int STATE_DELEGATING = 4;
56     // The user is performing something that might be a gesture.
57     public static final int STATE_GESTURE_DETECTING = 5;
58 
59     @IntDef({
60         STATE_CLEAR,
61         STATE_TOUCH_INTERACTING,
62         STATE_TOUCH_EXPLORING,
63         STATE_DRAGGING,
64         STATE_DELEGATING,
65         STATE_GESTURE_DETECTING
66     })
67     public @interface State {}
68 
69     // The current state of the touch explorer.
70     private int mState = STATE_CLEAR;
71     // Helper class to track received pointers.
72     // Todo: collapse or hide this class so multiple classes don't modify it.
73     private final ReceivedPointerTracker mReceivedPointerTracker;
74     // The most recently received motion event.
75     private MotionEvent mLastReceivedEvent;
76     // The accompanying raw event without any transformations.
77     private MotionEvent mLastReceivedRawEvent;
78     // The id of the last touch explored window.
79     private int mLastTouchedWindowId;
80     // The last injected hover event.
81     private MotionEvent mLastInjectedHoverEvent;
82     // The last injected hover event used for performing clicks.
83     private MotionEvent mLastInjectedHoverEventForClick;
84     // The time of the last injected down.
85     private long mLastInjectedDownEventTime;
86     // Keep track of which pointers sent to the system are down.
87     private int mInjectedPointersDown;
88 
TouchState()89     public TouchState() {
90         mReceivedPointerTracker = new ReceivedPointerTracker();
91     }
92 
93     /** Clears the internal shared state. */
clear()94     public void clear() {
95         setState(STATE_CLEAR);
96         // Reset the pointer trackers.
97         if (mLastReceivedEvent != null) {
98             mLastReceivedEvent.recycle();
99             mLastReceivedEvent = null;
100         }
101         mLastTouchedWindowId = -1;
102         mReceivedPointerTracker.clear();
103         mInjectedPointersDown = 0;
104     }
105 
106     /**
107      * Updates the state in response to a touch event received by TouchExplorer.
108      *
109      * @param rawEvent The raw touch event.
110      */
onReceivedMotionEvent(MotionEvent rawEvent)111     public void onReceivedMotionEvent(MotionEvent rawEvent) {
112         if (mLastReceivedEvent != null) {
113             mLastReceivedEvent.recycle();
114         }
115         if (mLastReceivedRawEvent != null) {
116             mLastReceivedRawEvent.recycle();
117         }
118         mLastReceivedEvent = MotionEvent.obtain(rawEvent);
119         mReceivedPointerTracker.onMotionEvent(rawEvent);
120     }
121 
122     /**
123      * Processes an injected {@link MotionEvent} event.
124      *
125      * @param event The event to process.
126      */
onInjectedMotionEvent(MotionEvent event)127     void onInjectedMotionEvent(MotionEvent event) {
128         final int action = event.getActionMasked();
129         final int pointerId = event.getPointerId(event.getActionIndex());
130         final int pointerFlag = (1 << pointerId);
131         switch (action) {
132             case MotionEvent.ACTION_DOWN:
133             case MotionEvent.ACTION_POINTER_DOWN:
134                 mInjectedPointersDown |= pointerFlag;
135                 mLastInjectedDownEventTime = event.getDownTime();
136                 break;
137             case MotionEvent.ACTION_UP:
138             case MotionEvent.ACTION_POINTER_UP:
139                 mInjectedPointersDown &= ~pointerFlag;
140                 if (mInjectedPointersDown == 0) {
141                     mLastInjectedDownEventTime = 0;
142                 }
143                 break;
144             case MotionEvent.ACTION_HOVER_ENTER:
145             case MotionEvent.ACTION_HOVER_MOVE:
146                 if (mLastInjectedHoverEvent != null) {
147                     mLastInjectedHoverEvent.recycle();
148                 }
149                 mLastInjectedHoverEvent = MotionEvent.obtain(event);
150                 break;
151             case MotionEvent.ACTION_HOVER_EXIT:
152                 if (mLastInjectedHoverEvent != null) {
153                     mLastInjectedHoverEvent.recycle();
154                 }
155                 mLastInjectedHoverEvent = MotionEvent.obtain(event);
156                 if (mLastInjectedHoverEventForClick != null) {
157                     mLastInjectedHoverEventForClick.recycle();
158                 }
159                 mLastInjectedHoverEventForClick = MotionEvent.obtain(event);
160                 break;
161         }
162         if (DEBUG) {
163             Slog.i(LOG_TAG, "Injected pointer:\n" + toString());
164         }
165     }
166 
167     /** Updates state in response to an accessibility event received from the outside. */
onReceivedAccessibilityEvent(AccessibilityEvent event)168     public void onReceivedAccessibilityEvent(AccessibilityEvent event) {
169         // If a new window opens or the accessibility focus moves we no longer
170         // want to click/long press on the last touch explored location.
171         switch (event.getEventType()) {
172             case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
173             case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED:
174                 if (mLastInjectedHoverEventForClick != null) {
175                     mLastInjectedHoverEventForClick.recycle();
176                     mLastInjectedHoverEventForClick = null;
177                 }
178                 mLastTouchedWindowId = -1;
179                 break;
180             case AccessibilityEvent.TYPE_VIEW_HOVER_ENTER:
181             case AccessibilityEvent.TYPE_VIEW_HOVER_EXIT:
182                 mLastTouchedWindowId = event.getWindowId();
183                 break;
184         }
185     }
186 
onInjectedAccessibilityEvent(int type)187     public void onInjectedAccessibilityEvent(int type) {
188         // The below state transitions go here because the related events are often sent on a
189         // delay.
190         // This allows state to accurately reflect the state in the moment.
191         // TODO: replaced the delayed event senders with delayed state transitions
192         // so that state transitions trigger events rather than events triggering state
193         // transitions.
194         switch (type) {
195             case AccessibilityEvent.TYPE_TOUCH_INTERACTION_START:
196                 startTouchInteracting();
197                 break;
198             case AccessibilityEvent.TYPE_TOUCH_INTERACTION_END:
199                 clear();
200                 break;
201             case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START:
202                 startTouchExploring();
203                 break;
204             case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END:
205                 startTouchInteracting();
206                 break;
207             case AccessibilityEvent.TYPE_GESTURE_DETECTION_START:
208                 startGestureDetecting();
209                 break;
210             case AccessibilityEvent.TYPE_GESTURE_DETECTION_END:
211                 // Clear to make sure that we don't accidentally execute passthrough, and that we
212                 // are ready for the next interaction.
213                 clear();
214                 break;
215             default:
216                 break;
217         }
218     }
219 
220     @State
getState()221     public int getState() {
222         return mState;
223     }
224 
225     /** Transitions to a new state. */
setState(@tate int state)226     public void setState(@State int state) {
227         if (mState == state) return;
228         if (DEBUG) {
229             Slog.i(LOG_TAG, getStateSymbolicName(mState) + "->" + getStateSymbolicName(state));
230         }
231         mState = state;
232     }
233 
isTouchExploring()234     public boolean isTouchExploring() {
235         return mState == STATE_TOUCH_EXPLORING;
236     }
237 
238     /** Starts touch exploration. */
startTouchExploring()239     public void startTouchExploring() {
240         setState(STATE_TOUCH_EXPLORING);
241     }
242 
isDelegating()243     public boolean isDelegating() {
244         return mState == STATE_DELEGATING;
245     }
246 
247     /** Starts delegating gestures to the view hierarchy. */
startDelegating()248     public void startDelegating() {
249         setState(STATE_DELEGATING);
250     }
251 
isGestureDetecting()252     public boolean isGestureDetecting() {
253         return mState == STATE_GESTURE_DETECTING;
254     }
255 
256     /** Initiates gesture detection. */
startGestureDetecting()257     public void startGestureDetecting() {
258         setState(STATE_GESTURE_DETECTING);
259     }
260 
isDragging()261     public boolean isDragging() {
262         return mState == STATE_DRAGGING;
263     }
264 
265     /** Starts a dragging gesture. */
startDragging()266     public void startDragging() {
267         setState(STATE_DRAGGING);
268     }
269 
isTouchInteracting()270     public boolean isTouchInteracting() {
271         return mState == STATE_TOUCH_INTERACTING;
272     }
273 
274     /**
275      * Transitions to the touch interacting state, where we attempt to figure out what the user is
276      * doing.
277      */
startTouchInteracting()278     public void startTouchInteracting() {
279         setState(STATE_TOUCH_INTERACTING);
280     }
281 
isClear()282     public boolean isClear() {
283         return mState == STATE_CLEAR;
284     }
285     /** Returns a string representation of the current state. */
toString()286     public String toString() {
287         return "TouchState { " + "mState: " + getStateSymbolicName(mState) + " }";
288     }
289     /** Returns a string representation of the specified state. */
getStateSymbolicName(int state)290     public static String getStateSymbolicName(int state) {
291         switch (state) {
292             case STATE_CLEAR:
293                 return "STATE_CLEAR";
294             case STATE_TOUCH_INTERACTING:
295                 return "STATE_TOUCH_INTERACTING";
296             case STATE_TOUCH_EXPLORING:
297                 return "STATE_TOUCH_EXPLORING";
298             case STATE_DRAGGING:
299                 return "STATE_DRAGGING";
300             case STATE_DELEGATING:
301                 return "STATE_DELEGATING";
302             case STATE_GESTURE_DETECTING:
303                 return "STATE_GESTURE_DETECTING";
304             default:
305                 return "Unknown state: " + state;
306         }
307     }
308 
getReceivedPointerTracker()309     public ReceivedPointerTracker getReceivedPointerTracker() {
310         return mReceivedPointerTracker;
311     }
312 
313     /** @return The last received event. */
getLastReceivedEvent()314     public MotionEvent getLastReceivedEvent() {
315         return mLastReceivedEvent;
316     }
317 
318     /** @return The the last injected hover event. */
getLastInjectedHoverEvent()319     public MotionEvent getLastInjectedHoverEvent() {
320         return mLastInjectedHoverEvent;
321     }
322 
323     /** @return The time of the last injected down event. */
getLastInjectedDownEventTime()324     public long getLastInjectedDownEventTime() {
325         return mLastInjectedDownEventTime;
326     }
327 
getLastTouchedWindowId()328     public int getLastTouchedWindowId() {
329         return mLastTouchedWindowId;
330     }
331 
332     /** @return The number of down pointers injected to the view hierarchy. */
getInjectedPointerDownCount()333     public int getInjectedPointerDownCount() {
334         return Integer.bitCount(mInjectedPointersDown);
335     }
336 
337     /** @return The bits of the injected pointers that are down. */
getInjectedPointersDown()338     public int getInjectedPointersDown() {
339         return mInjectedPointersDown;
340     }
341 
342     /**
343      * Whether an injected pointer is down.
344      *
345      * @param pointerId The unique pointer id.
346      * @return True if the pointer is down.
347      */
isInjectedPointerDown(int pointerId)348     public boolean isInjectedPointerDown(int pointerId) {
349         final int pointerFlag = (1 << pointerId);
350         return (mInjectedPointersDown & pointerFlag) != 0;
351     }
352 
353     /** @return The the last injected hover event used for a click. */
getLastInjectedHoverEventForClick()354     public MotionEvent getLastInjectedHoverEventForClick() {
355         return mLastInjectedHoverEventForClick;
356     }
357 
358     /** This class tracks where and when a pointer went down. It does not track its movement. */
359     class ReceivedPointerTracker {
360         private static final String LOG_TAG_RECEIVED_POINTER_TRACKER = "ReceivedPointerTracker";
361 
362         private final PointerDownInfo[] mReceivedPointers = new PointerDownInfo[MAX_POINTER_COUNT];
363 
364         // Which pointers are down.
365         private int mReceivedPointersDown;
366 
367         // The edge flags of the last received down event.
368         private int mLastReceivedDownEdgeFlags;
369 
370         // Primary pointer which is either the first that went down
371         // or if it goes up the next one that most recently went down.
372         private int mPrimaryPointerId;
373 
ReceivedPointerTracker()374         ReceivedPointerTracker() {
375             clear();
376         }
377 
378         /** Clears the internals state. */
clear()379         public void clear() {
380             mReceivedPointersDown = 0;
381             mPrimaryPointerId = 0;
382             for (int i = 0; i < MAX_POINTER_COUNT; ++i) {
383                 mReceivedPointers[i] = new PointerDownInfo();
384             }
385         }
386 
387         /**
388          * Processes a received {@link MotionEvent} event.
389          *
390          * @param event The event to process.
391          */
onMotionEvent(MotionEvent event)392         public void onMotionEvent(MotionEvent event) {
393             final int action = event.getActionMasked();
394             switch (action) {
395                 case MotionEvent.ACTION_DOWN:
396                     handleReceivedPointerDown(event.getActionIndex(), event);
397                     break;
398                 case MotionEvent.ACTION_POINTER_DOWN:
399                     handleReceivedPointerDown(event.getActionIndex(), event);
400                     break;
401                 case MotionEvent.ACTION_UP:
402                     handleReceivedPointerUp(event.getActionIndex(), event);
403                     break;
404                 case MotionEvent.ACTION_POINTER_UP:
405                     handleReceivedPointerUp(event.getActionIndex(), event);
406                     break;
407             }
408             if (DEBUG) {
409                 Slog.i(LOG_TAG_RECEIVED_POINTER_TRACKER, "Received pointer:\n" + toString());
410             }
411         }
412 
413         /** @return The number of received pointers that are down. */
getReceivedPointerDownCount()414         public int getReceivedPointerDownCount() {
415             return Integer.bitCount(mReceivedPointersDown);
416         }
417 
418         /**
419          * Whether an received pointer is down.
420          *
421          * @param pointerId The unique pointer id.
422          * @return True if the pointer is down.
423          */
isReceivedPointerDown(int pointerId)424         public boolean isReceivedPointerDown(int pointerId) {
425             final int pointerFlag = (1 << pointerId);
426             return (mReceivedPointersDown & pointerFlag) != 0;
427         }
428 
429         /**
430          * @param pointerId The unique pointer id.
431          * @return The X coordinate where the pointer went down.
432          */
getReceivedPointerDownX(int pointerId)433         public float getReceivedPointerDownX(int pointerId) {
434             return mReceivedPointers[pointerId].mX;
435         }
436 
437         /**
438          * @param pointerId The unique pointer id.
439          * @return The Y coordinate where the pointer went down.
440          */
getReceivedPointerDownY(int pointerId)441         public float getReceivedPointerDownY(int pointerId) {
442             return mReceivedPointers[pointerId].mY;
443         }
444 
445         /**
446          * @param pointerId The unique pointer id.
447          * @return The time when the pointer went down.
448          */
getReceivedPointerDownTime(int pointerId)449         public long getReceivedPointerDownTime(int pointerId) {
450             return mReceivedPointers[pointerId].mTime;
451         }
452 
453         /** @return The id of the primary pointer. */
getPrimaryPointerId()454         public int getPrimaryPointerId() {
455             if (mPrimaryPointerId == INVALID_POINTER_ID) {
456                 mPrimaryPointerId = findPrimaryPointerId();
457             }
458             return mPrimaryPointerId;
459         }
460 
461         /** @return The edge flags of the last received down event. */
getLastReceivedDownEdgeFlags()462         public int getLastReceivedDownEdgeFlags() {
463             return mLastReceivedDownEdgeFlags;
464         }
465 
466         /**
467          * Handles a received pointer down event.
468          *
469          * @param pointerIndex The index of the pointer that has changed.
470          * @param event The event to be handled.
471          */
handleReceivedPointerDown(int pointerIndex, MotionEvent event)472         private void handleReceivedPointerDown(int pointerIndex, MotionEvent event) {
473             final int pointerId = event.getPointerId(pointerIndex);
474             final int pointerFlag = (1 << pointerId);
475             mLastReceivedDownEdgeFlags = event.getEdgeFlags();
476 
477             mReceivedPointersDown |= pointerFlag;
478             mReceivedPointers[pointerId].set(
479                     event.getX(pointerIndex), event.getY(pointerIndex), event.getEventTime());
480 
481             mPrimaryPointerId = pointerId;
482         }
483 
484         /**
485          * Handles a received pointer up event.
486          *
487          * @param pointerIndex The index of the pointer that has changed.
488          * @param event The event to be handled.
489          */
handleReceivedPointerUp(int pointerIndex, MotionEvent event)490         private void handleReceivedPointerUp(int pointerIndex, MotionEvent event) {
491             final int pointerId = event.getPointerId(pointerIndex);
492             final int pointerFlag = (1 << pointerId);
493             mReceivedPointersDown &= ~pointerFlag;
494             mReceivedPointers[pointerId].clear();
495             if (mPrimaryPointerId == pointerId) {
496                 mPrimaryPointerId = INVALID_POINTER_ID;
497             }
498         }
499 
500         /** @return The primary pointer id. */
findPrimaryPointerId()501         private int findPrimaryPointerId() {
502             int primaryPointerId = INVALID_POINTER_ID;
503             long minDownTime = Long.MAX_VALUE;
504 
505             // Find the pointer that went down first.
506             int pointerIdBits = mReceivedPointersDown;
507             while (pointerIdBits > 0) {
508                 final int pointerId = Integer.numberOfTrailingZeros(pointerIdBits);
509                 pointerIdBits &= ~(1 << pointerId);
510                 final long downPointerTime = mReceivedPointers[pointerId].mTime;
511                 if (downPointerTime < minDownTime) {
512                     minDownTime = downPointerTime;
513                     primaryPointerId = pointerId;
514                 }
515             }
516             return primaryPointerId;
517         }
518 
519         @Override
toString()520         public String toString() {
521             StringBuilder builder = new StringBuilder();
522             builder.append("=========================");
523             builder.append("\nDown pointers #");
524             builder.append(getReceivedPointerDownCount());
525             builder.append(" [ ");
526             for (int i = 0; i < MAX_POINTER_COUNT; i++) {
527                 if (isReceivedPointerDown(i)) {
528                     builder.append(i);
529                     builder.append(" ");
530                 }
531             }
532             builder.append("]");
533             builder.append("\nPrimary pointer id [ ");
534             builder.append(getPrimaryPointerId());
535             builder.append(" ]");
536             builder.append("\n=========================");
537             return builder.toString();
538         }
539     }
540 
541     /**
542      * This class tracks where and when an individual pointer went down. Note that it does not track
543      * when it went up.
544      */
545     class PointerDownInfo {
546         private float mX;
547         private float mY;
548         private long mTime;
549 
set(float x, float y, long time)550         public void set(float x, float y, long time) {
551             mX = x;
552             mY = y;
553             mTime = time;
554         }
555 
clear()556         public void clear() {
557             mX = 0;
558             mY = 0;
559             mTime = 0;
560         }
561     }
562 }
563