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 com.android.server.accessibility.gestures.TouchExplorer.DEBUG;
20 import static com.android.server.accessibility.gestures.TouchState.ALL_POINTER_ID_BITS;
21 import static com.android.server.accessibility.gestures.TouchState.MAX_POINTER_COUNT;
22 
23 import android.content.Context;
24 import android.graphics.Point;
25 import android.util.Slog;
26 import android.view.MotionEvent;
27 import android.view.MotionEvent.PointerCoords;
28 import android.view.MotionEvent.PointerProperties;
29 import android.view.accessibility.AccessibilityEvent;
30 import android.view.accessibility.AccessibilityManager;
31 
32 import com.android.server.accessibility.AccessibilityManagerService;
33 import com.android.server.accessibility.EventStreamTransformation;
34 import com.android.server.policy.WindowManagerPolicy;
35 
36 /**
37  * This class dispatches motion events and accessibility events relating to touch exploration and
38  * gesture dispatch. TouchExplorer is responsible for insuring that the receiver of motion events is
39  * set correctly so that events go to the right place.
40  */
41 class EventDispatcher {
42     private static final String LOG_TAG = "EventDispatcher";
43     private static final int CLICK_LOCATION_NONE = 0;
44     private static final int CLICK_LOCATION_ACCESSIBILITY_FOCUS = 1;
45     private static final int CLICK_LOCATION_LAST_TOUCH_EXPLORED = 2;
46 
47     private final AccessibilityManagerService mAms;
48     private Context mContext;
49     // The receiver of motion events.
50     private EventStreamTransformation mReceiver;
51 
52     // The long pressing pointer id if coordinate remapping is needed for double tap and hold
53     private int mLongPressingPointerId = -1;
54 
55     // The long pressing pointer X if coordinate remapping is needed for double tap and hold.
56     private int mLongPressingPointerDeltaX;
57 
58     // The long pressing pointer Y if coordinate remapping is needed for double tap and hold.
59     private int mLongPressingPointerDeltaY;
60 
61     // Temporary point to avoid instantiation.
62     private final Point mTempPoint = new Point();
63 
64     private TouchState mState;
65 
EventDispatcher( Context context, AccessibilityManagerService ams, EventStreamTransformation receiver, TouchState state)66     EventDispatcher(
67             Context context,
68             AccessibilityManagerService ams,
69             EventStreamTransformation receiver,
70             TouchState state) {
71         mContext = context;
72         mAms = ams;
73         mReceiver = receiver;
74         mState = state;
75     }
76 
setReceiver(EventStreamTransformation receiver)77     public void setReceiver(EventStreamTransformation receiver) {
78         mReceiver = receiver;
79     }
80 
81     /**
82      * Sends an event.
83      *
84      * @param prototype The prototype from which to create the injected events.
85      * @param action The action of the event.
86      * @param rawEvent The original event prior to magnification or other transformations.
87      * @param pointerIdBits The bits of the pointers to send.
88      * @param policyFlags The policy flags associated with the event.
89      */
sendMotionEvent( MotionEvent prototype, int action, MotionEvent rawEvent, int pointerIdBits, int policyFlags)90     void sendMotionEvent(
91             MotionEvent prototype,
92             int action,
93             MotionEvent rawEvent,
94             int pointerIdBits,
95             int policyFlags) {
96         prototype.setAction(action);
97 
98         MotionEvent event = null;
99         if (pointerIdBits == ALL_POINTER_ID_BITS) {
100             event = prototype;
101         } else {
102             try {
103                 event = prototype.split(pointerIdBits);
104             } catch (IllegalArgumentException e) {
105                 Slog.e(LOG_TAG, "sendMotionEvent: Failed to split motion event: " + e);
106                 return;
107             }
108         }
109         if (action == MotionEvent.ACTION_DOWN) {
110             event.setDownTime(event.getEventTime());
111         } else {
112             event.setDownTime(mState.getLastInjectedDownEventTime());
113         }
114         // If the user is long pressing but the long pressing pointer
115         // was not exactly over the accessibility focused item we need
116         // to remap the location of that pointer so the user does not
117         // have to explicitly touch explore something to be able to
118         // long press it, or even worse to avoid the user long pressing
119         // on the wrong item since click and long press behave differently.
120         if (mLongPressingPointerId >= 0) {
121             event = offsetEvent(event, -mLongPressingPointerDeltaX, -mLongPressingPointerDeltaY);
122         }
123 
124         if (DEBUG) {
125             Slog.d(
126                     LOG_TAG,
127                     "Injecting event: "
128                             + event
129                             + ", policyFlags=0x"
130                             + Integer.toHexString(policyFlags));
131         }
132 
133         // Make sure that the user will see the event.
134         policyFlags |= WindowManagerPolicy.FLAG_PASS_TO_USER;
135         if (mReceiver != null) {
136             mReceiver.onMotionEvent(event, rawEvent, policyFlags);
137         } else {
138             Slog.e(LOG_TAG, "Error sending event: no receiver specified.");
139         }
140         mState.onInjectedMotionEvent(event);
141 
142         if (event != prototype) {
143             event.recycle();
144         }
145     }
146 
147     /**
148      * Sends an accessibility event of the given type.
149      *
150      * @param type The event type.
151      */
sendAccessibilityEvent(int type)152     void sendAccessibilityEvent(int type) {
153         AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext);
154         if (accessibilityManager.isEnabled()) {
155             AccessibilityEvent event = AccessibilityEvent.obtain(type);
156             event.setWindowId(mAms.getActiveWindowId());
157             accessibilityManager.sendAccessibilityEvent(event);
158             if (DEBUG) {
159                 Slog.d(
160                         LOG_TAG,
161                         "Sending accessibility event" + AccessibilityEvent.eventTypeToString(type));
162             }
163         }
164         // Todo: get rid of this and have TouchState control the sending of events rather than react
165         // to it.
166         mState.onInjectedAccessibilityEvent(type);
167     }
168 
169     @Override
toString()170     public String toString() {
171         StringBuilder builder = new StringBuilder();
172         builder.append("=========================");
173         builder.append("\nDown pointers #");
174         builder.append(Integer.bitCount(mState.getInjectedPointersDown()));
175         builder.append(" [ ");
176         for (int i = 0; i < MAX_POINTER_COUNT; i++) {
177             if (mState.isInjectedPointerDown(i)) {
178                 builder.append(i);
179                 builder.append(" ");
180             }
181         }
182         builder.append("]");
183         builder.append("\n=========================");
184         return builder.toString();
185     }
186 
187     /**
188      * /** Offsets all pointers in the given event by adding the specified X and Y offsets.
189      *
190      * @param event The event to offset.
191      * @param offsetX The X offset.
192      * @param offsetY The Y offset.
193      * @return An event with the offset pointers or the original event if both offsets are zero.
194      */
offsetEvent(MotionEvent event, int offsetX, int offsetY)195     private MotionEvent offsetEvent(MotionEvent event, int offsetX, int offsetY) {
196         if (offsetX == 0 && offsetY == 0) {
197             return event;
198         }
199         final int remappedIndex = event.findPointerIndex(mLongPressingPointerId);
200         final int pointerCount = event.getPointerCount();
201         PointerProperties[] props = PointerProperties.createArray(pointerCount);
202         PointerCoords[] coords = PointerCoords.createArray(pointerCount);
203         for (int i = 0; i < pointerCount; i++) {
204             event.getPointerProperties(i, props[i]);
205             event.getPointerCoords(i, coords[i]);
206             if (i == remappedIndex) {
207                 coords[i].x += offsetX;
208                 coords[i].y += offsetY;
209             }
210         }
211         return MotionEvent.obtain(
212                 event.getDownTime(),
213                 event.getEventTime(),
214                 event.getAction(),
215                 event.getPointerCount(),
216                 props,
217                 coords,
218                 event.getMetaState(),
219                 event.getButtonState(),
220                 1.0f,
221                 1.0f,
222                 event.getDeviceId(),
223                 event.getEdgeFlags(),
224                 event.getSource(),
225                 event.getDisplayId(),
226                 event.getFlags());
227     }
228 
229     /**
230      * Computes the action for an injected event based on a masked action and a pointer index.
231      *
232      * @param actionMasked The masked action.
233      * @param pointerIndex The index of the pointer which has changed.
234      * @return The action to be used for injection.
235      */
computeInjectionAction(int actionMasked, int pointerIndex)236     private int computeInjectionAction(int actionMasked, int pointerIndex) {
237         switch (actionMasked) {
238             case MotionEvent.ACTION_DOWN:
239             case MotionEvent.ACTION_POINTER_DOWN:
240                 // Compute the action based on how many down pointers are injected.
241                 if (mState.getInjectedPointerDownCount() == 0) {
242                     return MotionEvent.ACTION_DOWN;
243                 } else {
244                     return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
245                             | MotionEvent.ACTION_POINTER_DOWN;
246                 }
247             case MotionEvent.ACTION_POINTER_UP:
248                 // Compute the action based on how many down pointers are injected.
249                 if (mState.getInjectedPointerDownCount() == 1) {
250                     return MotionEvent.ACTION_UP;
251                 } else {
252                     return (pointerIndex << MotionEvent.ACTION_POINTER_INDEX_SHIFT)
253                             | MotionEvent.ACTION_POINTER_UP;
254                 }
255             default:
256                 return actionMasked;
257         }
258     }
259 
260     /**
261      * Sends down events to the view hierarchy for all pointers which are not already being
262      * delivered i.e. pointers that are not yet injected.
263      *
264      * @param prototype The prototype from which to create the injected events.
265      * @param policyFlags The policy flags associated with the event.
266      */
sendDownForAllNotInjectedPointers(MotionEvent prototype, int policyFlags)267     void sendDownForAllNotInjectedPointers(MotionEvent prototype, int policyFlags) {
268 
269         // Inject the injected pointers.
270         int pointerIdBits = 0;
271         final int pointerCount = prototype.getPointerCount();
272         for (int i = 0; i < pointerCount; i++) {
273             final int pointerId = prototype.getPointerId(i);
274             // Do not send event for already delivered pointers.
275             if (!mState.isInjectedPointerDown(pointerId)) {
276                 pointerIdBits |= (1 << pointerId);
277                 final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
278                 sendMotionEvent(
279                         prototype,
280                         action,
281                         mState.getLastReceivedEvent(),
282                         pointerIdBits,
283                         policyFlags);
284             }
285         }
286     }
287 
288     /**
289      * Sends down events to the view hierarchy for all pointers which are not already being
290      * delivered with original down location. i.e. pointers that are not yet injected. The down time
291      * is also replaced by the original one.
292      *
293      *
294      * @param prototype The prototype from which to create the injected events.
295      * @param policyFlags The policy flags associated with the event.
296      */
sendDownForAllNotInjectedPointersWithOriginalDown(MotionEvent prototype, int policyFlags)297     void sendDownForAllNotInjectedPointersWithOriginalDown(MotionEvent prototype, int policyFlags) {
298         // Inject the injected pointers.
299         int pointerIdBits = 0;
300         final int pointerCount = prototype.getPointerCount();
301         final MotionEvent event = computeInjectionDownEvent(prototype);
302         for (int i = 0; i < pointerCount; i++) {
303             final int pointerId = prototype.getPointerId(i);
304             // Do not send event for already delivered pointers.
305             if (!mState.isInjectedPointerDown(pointerId)) {
306                 pointerIdBits |= (1 << pointerId);
307                 final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i);
308                 sendMotionEvent(
309                         event,
310                         action,
311                         mState.getLastReceivedEvent(),
312                         pointerIdBits,
313                         policyFlags);
314             }
315         }
316     }
317 
computeInjectionDownEvent(MotionEvent prototype)318     private MotionEvent computeInjectionDownEvent(MotionEvent prototype) {
319         final int pointerCount = prototype.getPointerCount();
320         if (pointerCount != mState.getReceivedPointerTracker().getReceivedPointerDownCount()) {
321             Slog.w(LOG_TAG, "The pointer count doesn't match the received count.");
322             return MotionEvent.obtain(prototype);
323         }
324         MotionEvent.PointerCoords[] coords = new MotionEvent.PointerCoords[pointerCount];
325         MotionEvent.PointerProperties[] properties =
326                 new MotionEvent.PointerProperties[pointerCount];
327         for (int i = 0; i < pointerCount; ++i) {
328             final int pointerId = prototype.getPointerId(i);
329             final float x = mState.getReceivedPointerTracker().getReceivedPointerDownX(pointerId);
330             final float y = mState.getReceivedPointerTracker().getReceivedPointerDownY(pointerId);
331             coords[i] = new MotionEvent.PointerCoords();
332             coords[i].x = x;
333             coords[i].y = y;
334             properties[i] = new MotionEvent.PointerProperties();
335             properties[i].id = pointerId;
336             properties[i].toolType = MotionEvent.TOOL_TYPE_FINGER;
337         }
338         MotionEvent event =
339                 MotionEvent.obtain(
340                         prototype.getDownTime(),
341                         // The event time is used for downTime while sending ACTION_DOWN. We adjust
342                         // it to avoid the motion velocity is too fast in the beginning after
343                         // Delegating.
344                         prototype.getDownTime(),
345                         prototype.getAction(),
346                         pointerCount,
347                         properties,
348                         coords,
349                         prototype.getMetaState(),
350                         prototype.getButtonState(),
351                         prototype.getXPrecision(),
352                         prototype.getYPrecision(),
353                         prototype.getDeviceId(),
354                         prototype.getEdgeFlags(),
355                         prototype.getSource(),
356                         prototype.getFlags());
357         return event;
358     }
359 
360     /**
361      *
362      * Sends up events to the view hierarchy for all pointers which are already being delivered i.e.
363      * pointers that are injected.
364      *
365      * @param prototype The prototype from which to create the injected events.
366      * @param policyFlags The policy flags associated with the event.
367      */
sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags)368     void sendUpForInjectedDownPointers(MotionEvent prototype, int policyFlags) {
369         int pointerIdBits = prototype.getPointerIdBits();
370         final int pointerCount = prototype.getPointerCount();
371         for (int i = 0; i < pointerCount; i++) {
372             final int pointerId = prototype.getPointerId(i);
373             // Skip non injected down pointers.
374             if (!mState.isInjectedPointerDown(pointerId)) {
375                 continue;
376             }
377             final int action = computeInjectionAction(MotionEvent.ACTION_POINTER_UP, i);
378             sendMotionEvent(
379                     prototype, action, mState.getLastReceivedEvent(), pointerIdBits, policyFlags);
380             pointerIdBits &= ~(1 << pointerId);
381         }
382     }
383 
longPressWithTouchEvents(MotionEvent event, int policyFlags)384     public boolean longPressWithTouchEvents(MotionEvent event, int policyFlags) {
385         final int pointerIndex = event.getActionIndex();
386         final int pointerId = event.getPointerId(pointerIndex);
387         Point clickLocation = mTempPoint;
388         final int result = computeClickLocation(clickLocation);
389         if (result == CLICK_LOCATION_NONE) {
390             return false;
391         }
392         mLongPressingPointerId = pointerId;
393         mLongPressingPointerDeltaX = (int) event.getX(pointerIndex) - clickLocation.x;
394         mLongPressingPointerDeltaY = (int) event.getY(pointerIndex) - clickLocation.y;
395         sendDownForAllNotInjectedPointers(event, policyFlags);
396         return true;
397     }
398 
clear()399     void clear() {
400         mLongPressingPointerId = -1;
401         mLongPressingPointerDeltaX = 0;
402         mLongPressingPointerDeltaY = 0;
403     }
404 
clickWithTouchEvents(MotionEvent event, MotionEvent rawEvent, int policyFlags)405     public void clickWithTouchEvents(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
406         final int pointerIndex = event.getActionIndex();
407         final int pointerId = event.getPointerId(pointerIndex);
408         Point clickLocation = mTempPoint;
409         final int result = computeClickLocation(clickLocation);
410         if (result == CLICK_LOCATION_NONE) {
411             Slog.e(LOG_TAG, "Unable to compute click location.");
412             // We can't send a click to no location, but the gesture was still
413             // consumed.
414             return;
415         }
416         // Do the click.
417         PointerProperties[] properties = new PointerProperties[1];
418         properties[0] = new PointerProperties();
419         event.getPointerProperties(pointerIndex, properties[0]);
420         PointerCoords[] coords = new PointerCoords[1];
421         coords[0] = new PointerCoords();
422         coords[0].x = clickLocation.x;
423         coords[0].y = clickLocation.y;
424         MotionEvent clickEvent =
425                 MotionEvent.obtain(
426                         event.getDownTime(),
427                         event.getEventTime(),
428                         MotionEvent.ACTION_DOWN,
429                         1,
430                         properties,
431                         coords,
432                         0,
433                         0,
434                         1.0f,
435                         1.0f,
436                         event.getDeviceId(),
437                         0,
438                         event.getSource(),
439                         event.getDisplayId(),
440                         event.getFlags());
441         final boolean targetAccessibilityFocus = (result == CLICK_LOCATION_ACCESSIBILITY_FOCUS);
442         sendActionDownAndUp(clickEvent, rawEvent, policyFlags, targetAccessibilityFocus);
443         clickEvent.recycle();
444     }
445 
computeClickLocation(Point outLocation)446     private int computeClickLocation(Point outLocation) {
447         if (mState.getLastInjectedHoverEventForClick() != null) {
448             final int lastExplorePointerIndex =
449                     mState.getLastInjectedHoverEventForClick().getActionIndex();
450             outLocation.x =
451                     (int) mState.getLastInjectedHoverEventForClick().getX(lastExplorePointerIndex);
452             outLocation.y =
453                     (int) mState.getLastInjectedHoverEventForClick().getY(lastExplorePointerIndex);
454             if (!mAms.accessibilityFocusOnlyInActiveWindow()
455                     || mState.getLastTouchedWindowId() == mAms.getActiveWindowId()) {
456                 if (mAms.getAccessibilityFocusClickPointInScreen(outLocation)) {
457                     return CLICK_LOCATION_ACCESSIBILITY_FOCUS;
458                 } else {
459                     return CLICK_LOCATION_LAST_TOUCH_EXPLORED;
460                 }
461             }
462         }
463         if (mAms.getAccessibilityFocusClickPointInScreen(outLocation)) {
464             return CLICK_LOCATION_ACCESSIBILITY_FOCUS;
465         }
466         return CLICK_LOCATION_NONE;
467     }
468 
sendActionDownAndUp( MotionEvent prototype, MotionEvent rawEvent, int policyFlags, boolean targetAccessibilityFocus)469     private void sendActionDownAndUp(
470             MotionEvent prototype,
471             MotionEvent rawEvent,
472             int policyFlags,
473             boolean targetAccessibilityFocus) {
474         // Tap with the pointer that last explored.
475         final int pointerId = prototype.getPointerId(prototype.getActionIndex());
476         final int pointerIdBits = (1 << pointerId);
477         prototype.setTargetAccessibilityFocus(targetAccessibilityFocus);
478         sendMotionEvent(prototype, MotionEvent.ACTION_DOWN, rawEvent, pointerIdBits, policyFlags);
479         prototype.setTargetAccessibilityFocus(targetAccessibilityFocus);
480         sendMotionEvent(prototype, MotionEvent.ACTION_UP, rawEvent, pointerIdBits, policyFlags);
481     }
482 }
483