• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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