1 /* 2 * Copyright (C) 2022 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.inputmethod; 18 19 import static android.view.InputDevice.SOURCE_STYLUS; 20 21 import android.annotation.AnyThread; 22 import android.annotation.Nullable; 23 import android.annotation.UiThread; 24 import android.hardware.input.InputManagerInternal; 25 import android.os.IBinder; 26 import android.os.Looper; 27 import android.util.Slog; 28 import android.view.BatchedInputEventReceiver; 29 import android.view.Choreographer; 30 import android.view.Display; 31 import android.view.InputChannel; 32 import android.view.InputEvent; 33 import android.view.InputEventReceiver; 34 import android.view.MotionEvent; 35 import android.view.SurfaceControl; 36 37 import com.android.server.LocalServices; 38 import com.android.server.wm.WindowManagerInternal; 39 40 import java.util.ArrayList; 41 import java.util.List; 42 import java.util.Objects; 43 import java.util.OptionalInt; 44 45 // TODO(b/210039666): See if we can make this class thread-safe. 46 final class HandwritingModeController { 47 48 public static final String TAG = HandwritingModeController.class.getSimpleName(); 49 // TODO(b/210039666): flip the flag. 50 static final boolean DEBUG = true; 51 private static final int EVENT_BUFFER_SIZE = 100; 52 53 // This must be the looper for the UiThread. 54 private final Looper mLooper; 55 private final InputManagerInternal mInputManagerInternal; 56 private final WindowManagerInternal mWindowManagerInternal; 57 58 private List<MotionEvent> mHandwritingBuffer; 59 private InputEventReceiver mHandwritingEventReceiver; 60 private Runnable mInkWindowInitRunnable; 61 private boolean mRecordingGesture; 62 private int mCurrentDisplayId; 63 64 private HandwritingEventReceiverSurface mHandwritingSurface; 65 66 private int mCurrentRequestId; 67 68 @AnyThread HandwritingModeController(Looper uiThreadLooper, Runnable inkWindowInitRunnable)69 HandwritingModeController(Looper uiThreadLooper, Runnable inkWindowInitRunnable) { 70 mLooper = uiThreadLooper; 71 mCurrentDisplayId = Display.INVALID_DISPLAY; 72 mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); 73 mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); 74 mCurrentRequestId = 0; 75 mInkWindowInitRunnable = inkWindowInitRunnable; 76 } 77 78 // TODO(b/210039666): Consider moving this to MotionEvent isStylusEvent(MotionEvent event)79 private static boolean isStylusEvent(MotionEvent event) { 80 if (!event.isFromSource(SOURCE_STYLUS)) { 81 return false; 82 } 83 final int tool = event.getToolType(0); 84 return tool == MotionEvent.TOOL_TYPE_STYLUS || tool == MotionEvent.TOOL_TYPE_ERASER; 85 } 86 87 /** 88 * Initializes the handwriting spy on the given displayId. 89 * 90 * This must be called from the UI Thread because it will start processing events using an 91 * InputEventReceiver that batches events according to the current thread's Choreographer. 92 */ 93 @UiThread initializeHandwritingSpy(int displayId)94 void initializeHandwritingSpy(int displayId) { 95 // When resetting, reuse resources if we are reinitializing on the same display. 96 reset(displayId == mCurrentDisplayId); 97 mCurrentDisplayId = displayId; 98 99 if (mHandwritingBuffer == null) { 100 mHandwritingBuffer = new ArrayList<>(EVENT_BUFFER_SIZE); 101 } 102 103 if (DEBUG) Slog.d(TAG, "Initializing handwriting spy monitor for display: " + displayId); 104 final String name = "stylus-handwriting-event-receiver-" + displayId; 105 final InputChannel channel = mInputManagerInternal.createInputChannel(name); 106 Objects.requireNonNull(channel, "Failed to create input channel"); 107 final SurfaceControl surface = 108 mHandwritingSurface != null ? mHandwritingSurface.getSurface() 109 : mWindowManagerInternal.getHandwritingSurfaceForDisplay(displayId); 110 if (surface == null) { 111 Slog.e(TAG, "Failed to create input surface"); 112 return; 113 } 114 115 mHandwritingSurface = new HandwritingEventReceiverSurface( 116 name, displayId, surface, channel); 117 118 // Use a dup of the input channel so that event processing can be paused by disposing the 119 // event receiver without causing a fd hangup. 120 mHandwritingEventReceiver = new BatchedInputEventReceiver.SimpleBatchedInputEventReceiver( 121 channel.dup(), mLooper, Choreographer.getInstance(), this::onInputEvent); 122 mCurrentRequestId++; 123 } 124 getCurrentRequestId()125 OptionalInt getCurrentRequestId() { 126 if (mHandwritingSurface == null) { 127 Slog.e(TAG, "Cannot get requestId: Handwriting was not initialized."); 128 return OptionalInt.empty(); 129 } 130 return OptionalInt.of(mCurrentRequestId); 131 } 132 isStylusGestureOngoing()133 boolean isStylusGestureOngoing() { 134 return mRecordingGesture; 135 } 136 137 /** 138 * Starts a {@link HandwritingSession} to transfer to the IME. 139 * 140 * This must be called from the UI Thread to avoid race conditions between processing more 141 * input events and disposing the input event receiver. 142 * @return the handwriting session to send to the IME, or null if the request was invalid. 143 */ 144 @UiThread 145 @Nullable startHandwritingSession( int requestId, int imePid, int imeUid, IBinder focusedWindowToken)146 HandwritingSession startHandwritingSession( 147 int requestId, int imePid, int imeUid, IBinder focusedWindowToken) { 148 if (mHandwritingSurface == null) { 149 Slog.e(TAG, "Cannot start handwriting session: Handwriting was not initialized."); 150 return null; 151 } 152 if (requestId != mCurrentRequestId) { 153 Slog.e(TAG, "Cannot start handwriting session: Invalid request id: " + requestId); 154 return null; 155 } 156 if (!mRecordingGesture || mHandwritingBuffer.isEmpty()) { 157 Slog.e(TAG, "Cannot start handwriting session: No stylus gesture is being recorded."); 158 return null; 159 } 160 Objects.requireNonNull(mHandwritingEventReceiver, 161 "Handwriting session was already transferred to IME."); 162 final MotionEvent downEvent = mHandwritingBuffer.get(0); 163 assert (downEvent.getActionMasked() == MotionEvent.ACTION_DOWN); 164 if (!mWindowManagerInternal.isPointInsideWindow( 165 focusedWindowToken, mCurrentDisplayId, downEvent.getRawX(), downEvent.getRawY())) { 166 Slog.e(TAG, "Cannot start handwriting session: " 167 + "Stylus gesture did not start inside the focused window."); 168 return null; 169 } 170 if (DEBUG) Slog.d(TAG, "Starting handwriting session in display: " + mCurrentDisplayId); 171 172 mInputManagerInternal.pilferPointers(mHandwritingSurface.getInputChannel().getToken()); 173 174 // Stop processing more events. 175 mHandwritingEventReceiver.dispose(); 176 mHandwritingEventReceiver = null; 177 mRecordingGesture = false; 178 179 if (mHandwritingSurface.isIntercepting()) { 180 throw new IllegalStateException( 181 "Handwriting surface should not be already intercepting."); 182 } 183 mHandwritingSurface.startIntercepting(imePid, imeUid); 184 185 return new HandwritingSession(mCurrentRequestId, mHandwritingSurface.getInputChannel(), 186 mHandwritingBuffer); 187 } 188 189 /** 190 * Reset the current handwriting session without initializing another session. 191 * 192 * This must be called from UI Thread to avoid race conditions between processing more input 193 * events and disposing the input event receiver. 194 */ 195 @UiThread reset()196 void reset() { 197 reset(false /* reinitializing */); 198 } 199 reset(boolean reinitializing)200 private void reset(boolean reinitializing) { 201 if (mHandwritingEventReceiver != null) { 202 mHandwritingEventReceiver.dispose(); 203 mHandwritingEventReceiver = null; 204 } 205 206 if (mHandwritingBuffer != null) { 207 mHandwritingBuffer.forEach(MotionEvent::recycle); 208 mHandwritingBuffer.clear(); 209 if (!reinitializing) { 210 mHandwritingBuffer = null; 211 } 212 } 213 214 if (mHandwritingSurface != null) { 215 mHandwritingSurface.getInputChannel().dispose(); 216 if (!reinitializing) { 217 mHandwritingSurface.remove(); 218 mHandwritingSurface = null; 219 } 220 } 221 222 mRecordingGesture = false; 223 } 224 onInputEvent(InputEvent ev)225 private boolean onInputEvent(InputEvent ev) { 226 if (mHandwritingEventReceiver == null) { 227 throw new IllegalStateException( 228 "Input Event should not be processed when IME has the spy channel."); 229 } 230 231 if (!(ev instanceof MotionEvent)) { 232 Slog.wtf(TAG, "Received non-motion event in stylus monitor."); 233 return false; 234 } 235 final MotionEvent event = (MotionEvent) ev; 236 if (!isStylusEvent(event)) { 237 return false; 238 } 239 if (event.getDisplayId() != mCurrentDisplayId) { 240 Slog.wtf(TAG, "Received stylus event associated with the incorrect display."); 241 return false; 242 } 243 244 onStylusEvent(event); 245 return true; 246 } 247 onStylusEvent(MotionEvent event)248 private void onStylusEvent(MotionEvent event) { 249 final int action = event.getActionMasked(); 250 251 if (mInkWindowInitRunnable != null && (action == MotionEvent.ACTION_HOVER_ENTER 252 || event.getAction() == MotionEvent.ACTION_HOVER_ENTER)) { 253 // Ask IMMS to make ink window ready. 254 mInkWindowInitRunnable.run(); 255 mInkWindowInitRunnable = null; 256 } 257 258 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) { 259 mRecordingGesture = false; 260 mHandwritingBuffer.clear(); 261 return; 262 } 263 264 if (action == MotionEvent.ACTION_DOWN) { 265 mRecordingGesture = true; 266 } 267 268 if (!mRecordingGesture) { 269 return; 270 } 271 272 if (mHandwritingBuffer.size() >= EVENT_BUFFER_SIZE) { 273 if (DEBUG) { 274 Slog.w(TAG, "Current gesture exceeds the buffer capacity." 275 + " The rest of the gesture will not be recorded."); 276 } 277 mRecordingGesture = false; 278 return; 279 } 280 281 mHandwritingBuffer.add(MotionEvent.obtain(event)); 282 } 283 284 static final class HandwritingSession { 285 private final int mRequestId; 286 private final InputChannel mHandwritingChannel; 287 private final List<MotionEvent> mRecordedEvents; 288 HandwritingSession(int requestId, InputChannel handwritingChannel, List<MotionEvent> recordedEvents)289 private HandwritingSession(int requestId, InputChannel handwritingChannel, 290 List<MotionEvent> recordedEvents) { 291 mRequestId = requestId; 292 mHandwritingChannel = handwritingChannel; 293 mRecordedEvents = recordedEvents; 294 } 295 getRequestId()296 int getRequestId() { 297 return mRequestId; 298 } 299 getHandwritingChannel()300 InputChannel getHandwritingChannel() { 301 return mHandwritingChannel; 302 } 303 getRecordedEvents()304 List<MotionEvent> getRecordedEvents() { 305 return mRecordedEvents; 306 } 307 } 308 } 309