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