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