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