• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.magnification;
18 
19 import static android.view.InputDevice.SOURCE_MOUSE;
20 import static android.view.InputDevice.SOURCE_STYLUS;
21 import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
22 import static android.view.MotionEvent.ACTION_CANCEL;
23 import static android.view.MotionEvent.ACTION_UP;
24 
25 import android.accessibilityservice.AccessibilityTrace;
26 import android.annotation.NonNull;
27 import android.util.Log;
28 import android.util.Slog;
29 import android.view.MotionEvent;
30 
31 import com.android.server.accessibility.AccessibilityTraceManager;
32 import com.android.server.accessibility.BaseEventStreamTransformation;
33 import com.android.server.accessibility.Flags;
34 
35 import java.util.ArrayDeque;
36 import java.util.Queue;
37 
38 /**
39  * A base class that detects gestures and defines common methods for magnification.
40  */
41 public abstract class MagnificationGestureHandler extends BaseEventStreamTransformation {
42 
43     protected final String mLogTag = this.getClass().getSimpleName();
44     protected static final boolean DEBUG_ALL = Log.isLoggable("MagnificationGestureHandler",
45             Log.DEBUG);
46     protected static final boolean DEBUG_EVENT_STREAM = false | DEBUG_ALL;
47     private final Queue<MotionEvent> mDebugInputEventHistory;
48     private final Queue<MotionEvent> mDebugOutputEventHistory;
49 
50     /**
51      * The logical display id.
52      */
53     protected final int mDisplayId;
54 
55     /**
56      * {@code true} if this detector should be "triggerable" by some
57      * external shortcut invoking {@link #notifyShortcutTriggered},
58      * {@code false} if it should ignore such triggers.
59      */
60     protected final boolean mDetectShortcutTrigger;
61 
62     /**
63      * {@code true} if this detector should detect and respond to single-finger triple-tap
64      * gestures for engaging and disengaging magnification,
65      * {@code false} if it should ignore such gestures
66      */
67     protected final boolean mDetectSingleFingerTripleTap;
68 
69     /**
70      * {@code true} if this detector should detect and respond to two-finger triple-tap
71      * gestures for engaging and disengaging magnification,
72      * {@code false} if it should ignore such gestures
73      */
74     protected final boolean mDetectTwoFingerTripleTap;
75 
76     /** Callback interface to report that magnification is interactive with a user. */
77     public interface Callback {
78         /**
79          * Called when the touch interaction is started by a user.
80          *
81          * @param displayId The logical display id
82          * @param mode The magnification mode
83          */
onTouchInteractionStart(int displayId, int mode)84         void onTouchInteractionStart(int displayId, int mode);
85 
86         /**
87          * Called when the touch interaction is ended by a user.
88          *
89          * @param displayId The logical display id
90          * @param mode The magnification mode
91          */
onTouchInteractionEnd(int displayId, int mode)92         void onTouchInteractionEnd(int displayId, int mode);
93     }
94 
95     private final AccessibilityTraceManager mTrace;
96     protected final Callback mCallback;
97 
MagnificationGestureHandler(int displayId, boolean detectSingleFingerTripleTap, boolean detectTwoFingerTripleTap, boolean detectShortcutTrigger, AccessibilityTraceManager trace, @NonNull Callback callback)98     protected MagnificationGestureHandler(int displayId,
99             boolean detectSingleFingerTripleTap,
100             boolean detectTwoFingerTripleTap,
101             boolean detectShortcutTrigger,
102             AccessibilityTraceManager trace,
103             @NonNull Callback callback) {
104         mDisplayId = displayId;
105         mDetectSingleFingerTripleTap = detectSingleFingerTripleTap;
106         mDetectTwoFingerTripleTap = Flags.enableMagnificationMultipleFingerMultipleTapGesture()
107                 && detectTwoFingerTripleTap;
108         mDetectShortcutTrigger = detectShortcutTrigger;
109         mTrace = trace;
110         mCallback = callback;
111 
112         mDebugInputEventHistory = DEBUG_EVENT_STREAM ? new ArrayDeque<>() : null;
113         mDebugOutputEventHistory = DEBUG_EVENT_STREAM ? new ArrayDeque<>() : null;
114     }
115 
116     @Override
onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)117     public final void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
118         if (DEBUG_ALL) {
119             Slog.i(mLogTag, "onMotionEvent(" + event + ")");
120         }
121         if (mTrace.isA11yTracingEnabledForTypes(
122                 AccessibilityTrace.FLAGS_INPUT_FILTER | AccessibilityTrace.FLAGS_GESTURE)) {
123             mTrace.logTrace("MagnificationGestureHandler.onMotionEvent",
124                     AccessibilityTrace.FLAGS_INPUT_FILTER | AccessibilityTrace.FLAGS_GESTURE,
125                     "event=" + event + ";rawEvent=" + rawEvent + ";policyFlags=" + policyFlags);
126         }
127         if (DEBUG_EVENT_STREAM) {
128             storeEventInto(mDebugInputEventHistory, event);
129         }
130         switch (event.getSource()) {
131             case SOURCE_TOUCHSCREEN: {
132                 if (magnificationShortcutExists()) {
133                     // Observe touchscreen events while magnification activation is detected.
134                     onMotionEventInternal(event, rawEvent, policyFlags);
135 
136                     final int action = event.getAction();
137                     if (action == MotionEvent.ACTION_DOWN) {
138                         mCallback.onTouchInteractionStart(mDisplayId, getMode());
139                     } else if (action == ACTION_UP || action == ACTION_CANCEL) {
140                         mCallback.onTouchInteractionEnd(mDisplayId, getMode());
141                     }
142                     // Return early: Do not dispatch event through normal eventing
143                     // flow, it has been fully consumed by the magnifier.
144                     return;
145                 }
146             } break;
147             case SOURCE_MOUSE:
148             case SOURCE_STYLUS: {
149                 if (magnificationShortcutExists()) {
150                     handleMouseOrStylusEvent(event, rawEvent, policyFlags);
151                 }
152             }
153                 break;
154             default:
155                 break;
156         }
157         // Dispatch event through normal eventing flow.
158         dispatchTransformedEvent(event, rawEvent, policyFlags);
159     }
160 
magnificationShortcutExists()161     private boolean magnificationShortcutExists() {
162         return (mDetectSingleFingerTripleTap
163                 || mDetectTwoFingerTripleTap
164                 || mDetectShortcutTrigger);
165     }
166 
dispatchTransformedEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)167     final void dispatchTransformedEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
168         if (DEBUG_EVENT_STREAM) {
169             storeEventInto(mDebugOutputEventHistory, event);
170             try {
171                 super.onMotionEvent(event, rawEvent, policyFlags);
172                 return;
173             } catch (Exception e) {
174                 throw new RuntimeException(
175                         "Exception downstream following input events: " + mDebugInputEventHistory
176                                 + "\nTransformed into output events: " + mDebugOutputEventHistory,
177                         e);
178             }
179         }
180         super.onMotionEvent(event, rawEvent, policyFlags);
181     }
182 
storeEventInto(Queue<MotionEvent> queue, MotionEvent event)183     private static void storeEventInto(Queue<MotionEvent> queue, MotionEvent event) {
184         queue.add(MotionEvent.obtain(event));
185         // Prune old events
186         while (!queue.isEmpty() && (event.getEventTime() - queue.peek().getEventTime() > 5000)) {
187             queue.remove().recycle();
188         }
189     }
190 
191     /**
192      * Called when this MagnificationGestureHandler handles the motion event.
193      */
onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags)194     abstract void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags);
195 
196     /**
197      * Called when this MagnificationGestureHandler should handle a mouse or stylus motion event,
198      * but not re-dispatch it when completed.
199      */
handleMouseOrStylusEvent( MotionEvent event, MotionEvent rawEvent, int policyFlags)200     abstract void handleMouseOrStylusEvent(
201             MotionEvent event, MotionEvent rawEvent, int policyFlags);
202 
203     /**
204      * Called when the shortcut target is magnification.
205      */
notifyShortcutTriggered()206     public void notifyShortcutTriggered() {
207         if (DEBUG_ALL) {
208             Slog.i(mLogTag, "notifyShortcutTriggered():");
209         }
210         if (mDetectShortcutTrigger) {
211             handleShortcutTriggered();
212         }
213     }
214 
215     /**
216      * Handles shortcut triggered event.
217      */
handleShortcutTriggered()218     abstract void handleShortcutTriggered();
219 
220     /**
221      * Indicates the magnification mode.
222      *
223      * @return the magnification mode of the handler
224      * @see android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN
225      * @see android.provider.Settings.Secure#ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW
226      */
getMode()227     public abstract int getMode();
228 }
229