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