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 com.android.text.flags.Flags.handwritingEndOfLineTap;
20 
21 import android.Manifest;
22 import android.annotation.AnyThread;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.RequiresPermission;
26 import android.annotation.UiThread;
27 import android.content.ComponentName;
28 import android.content.Context;
29 import android.content.pm.PackageManagerInternal;
30 import android.hardware.input.InputManager;
31 import android.hardware.input.InputManagerGlobal;
32 import android.os.Handler;
33 import android.os.IBinder;
34 import android.os.Looper;
35 import android.os.SystemClock;
36 import android.text.TextUtils;
37 import android.util.Slog;
38 import android.view.BatchedInputEventReceiver;
39 import android.view.Choreographer;
40 import android.view.Display;
41 import android.view.InputChannel;
42 import android.view.InputEvent;
43 import android.view.InputEventReceiver;
44 import android.view.MotionEvent;
45 import android.view.PointerIcon;
46 import android.view.SurfaceControl;
47 import android.view.View;
48 import android.view.inputmethod.InputMethodManager;
49 
50 import com.android.server.LocalServices;
51 import com.android.server.input.InputManagerInternal;
52 import com.android.server.wm.WindowManagerInternal;
53 
54 import java.util.ArrayList;
55 import java.util.List;
56 import java.util.Objects;
57 import java.util.OptionalInt;
58 
59 // TODO(b/210039666): See if we can make this class thread-safe.
60 final class HandwritingModeController {
61 
62     public static final String TAG = HandwritingModeController.class.getSimpleName();
63     static final boolean DEBUG = false;
64     // Use getHandwritingBufferSize() and not this value directly.
65     private static final int EVENT_BUFFER_SIZE = 100;
66     // A longer event buffer used for handwriting delegation
67     // TODO(b/210039666): make this device touch sampling rate dependent.
68     // Use getHandwritingBufferSize() and not this value directly.
69     private static final int LONG_EVENT_BUFFER_SIZE = EVENT_BUFFER_SIZE * 20;
70     private static final long HANDWRITING_DELEGATION_IDLE_TIMEOUT_MS = 3000;
71     private static final long AFTER_STYLUS_UP_ALLOW_PERIOD_MS = 200L;
72 
73     private final Context mContext;
74     // This must be the looper for the UiThread.
75     private final Looper mLooper;
76     private final InputManagerInternal mInputManagerInternal;
77     private final WindowManagerInternal mWindowManagerInternal;
78     private final PackageManagerInternal mPackageManagerInternal;
79 
80     private ArrayList<MotionEvent> mHandwritingBuffer;
81     private InputEventReceiver mHandwritingEventReceiver;
82     private Runnable mInkWindowInitRunnable;
83     private boolean mRecordingGesture;
84     private boolean mRecordingGestureAfterStylusUp;
85     private int mCurrentDisplayId;
86     // when set, package names are used for handwriting delegation.
87     private @Nullable String mDelegatePackageName;
88     private @Nullable String mDelegatorPackageName;
89     private boolean mDelegatorFromDefaultHomePackage;
90     private boolean mDelegationConnectionlessFlow;
91     private Runnable mDelegationIdleTimeoutRunnable;
92     private Handler mDelegationIdleTimeoutHandler;
93     private final Runnable mDiscardDelegationTextRunnable;
94     private HandwritingEventReceiverSurface mHandwritingSurface;
95 
96     private int mCurrentRequestId;
97 
98     @AnyThread
HandwritingModeController(Context context, Looper uiThreadLooper, Runnable inkWindowInitRunnable, Runnable discardDelegationTextRunnable)99     HandwritingModeController(Context context, Looper uiThreadLooper,
100             Runnable inkWindowInitRunnable,
101             Runnable discardDelegationTextRunnable) {
102         mContext = context;
103         mLooper = uiThreadLooper;
104         mCurrentDisplayId = Display.INVALID_DISPLAY;
105         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
106         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
107         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
108         mCurrentRequestId = 0;
109         mInkWindowInitRunnable = inkWindowInitRunnable;
110         mDiscardDelegationTextRunnable = discardDelegationTextRunnable;
111     }
112 
113     /**
114      * Initializes the handwriting spy on the given displayId.
115      *
116      * This must be called from the UI Thread because it will start processing events using an
117      * InputEventReceiver that batches events according to the current thread's Choreographer.
118      */
119     @UiThread
initializeHandwritingSpy(int displayId)120     void initializeHandwritingSpy(int displayId) {
121         // When resetting, reuse resources if we are reinitializing on the same display.
122         reset(displayId == mCurrentDisplayId);
123         mCurrentDisplayId = displayId;
124 
125         if (mHandwritingBuffer == null) {
126             mHandwritingBuffer = new ArrayList<>(getHandwritingBufferSize());
127         }
128 
129         if (DEBUG) Slog.d(TAG, "Initializing handwriting spy monitor for display: " + displayId);
130         final String name = "stylus-handwriting-event-receiver-" + displayId;
131         final InputChannel channel = mInputManagerInternal.createInputChannel(name);
132         Objects.requireNonNull(channel, "Failed to create input channel");
133         final SurfaceControl surface =
134                 mHandwritingSurface != null ? mHandwritingSurface.getSurface()
135                         : mWindowManagerInternal.getHandwritingSurfaceForDisplay(displayId);
136         if (surface == null) {
137             Slog.e(TAG, "Failed to create input surface");
138             return;
139         }
140 
141         mHandwritingSurface = new HandwritingEventReceiverSurface(
142                 name, displayId, surface, channel);
143 
144         // Use a dup of the input channel so that event processing can be paused by disposing the
145         // event receiver without causing a fd hangup.
146         mHandwritingEventReceiver = new BatchedInputEventReceiver.SimpleBatchedInputEventReceiver(
147                 channel.dup(), mLooper, Choreographer.getInstance(), this::onInputEvent);
148         mCurrentRequestId++;
149     }
150 
getCurrentRequestId()151     OptionalInt getCurrentRequestId() {
152         if (mHandwritingSurface == null) {
153             Slog.e(TAG, "Cannot get requestId: Handwriting was not initialized.");
154             return OptionalInt.empty();
155         }
156         return OptionalInt.of(mCurrentRequestId);
157     }
158 
setNotTouchable(boolean notTouchable)159     void setNotTouchable(boolean notTouchable) {
160         if (!getCurrentRequestId().isPresent()) {
161             return;
162         }
163         mHandwritingSurface.setNotTouchable(notTouchable);
164     }
165 
isStylusGestureOngoing()166     boolean isStylusGestureOngoing() {
167         if (mRecordingGestureAfterStylusUp && !mHandwritingBuffer.isEmpty()) {
168             // If it is less than AFTER_STYLUS_UP_ALLOW_PERIOD_MS after the stylus up event, return
169             // true so that handwriting can start.
170             MotionEvent lastEvent = mHandwritingBuffer.get(mHandwritingBuffer.size() - 1);
171             if (lastEvent.getActionMasked() == MotionEvent.ACTION_UP) {
172                 return SystemClock.uptimeMillis() - lastEvent.getEventTime()
173                         < AFTER_STYLUS_UP_ALLOW_PERIOD_MS;
174             }
175         }
176         return mRecordingGesture;
177     }
178 
hasOngoingStylusHandwritingSession()179     boolean hasOngoingStylusHandwritingSession() {
180         return mHandwritingSurface != null && mHandwritingSurface.isIntercepting();
181     }
182 
183     /**
184      * Prepare delegation of stylus handwriting to a different editor
185      * @see InputMethodManager#prepareStylusHandwritingDelegation(View, String)
186      */
prepareStylusHandwritingDelegation( int userId, @NonNull String delegatePackageName, @NonNull String delegatorPackageName, boolean connectionless)187     void prepareStylusHandwritingDelegation(
188             int userId, @NonNull String delegatePackageName, @NonNull String delegatorPackageName,
189             boolean connectionless) {
190         mDelegatePackageName = delegatePackageName;
191         mDelegatorPackageName = delegatorPackageName;
192         mDelegatorFromDefaultHomePackage = false;
193         // mDelegatorFromDefaultHomeActivity is only used in the cross-package delegation case.
194         // For same-package delegation, it doesn't need to be checked.
195         if (!delegatorPackageName.equals(delegatePackageName)) {
196             ComponentName defaultHomeActivity =
197                     mPackageManagerInternal.getDefaultHomeActivity(userId);
198             if (defaultHomeActivity != null) {
199                 mDelegatorFromDefaultHomePackage =
200                         delegatorPackageName.equals(defaultHomeActivity.getPackageName());
201             }
202         }
203         mDelegationConnectionlessFlow = connectionless;
204         if (!connectionless) {
205             if (mHandwritingBuffer == null) {
206                 mHandwritingBuffer = new ArrayList<>(getHandwritingBufferSize());
207             } else {
208                 mHandwritingBuffer.ensureCapacity(getHandwritingBufferSize());
209             }
210         }
211         scheduleHandwritingDelegationTimeout();
212     }
213 
getDelegatePackageName()214     @Nullable String getDelegatePackageName() {
215         return mDelegatePackageName;
216     }
217 
getDelegatorPackageName()218     @Nullable String getDelegatorPackageName() {
219         return mDelegatorPackageName;
220     }
221 
isDelegatorFromDefaultHomePackage()222     boolean isDelegatorFromDefaultHomePackage() {
223         return mDelegatorFromDefaultHomePackage;
224     }
225 
isDelegationUsingConnectionlessFlow()226     boolean isDelegationUsingConnectionlessFlow() {
227         return mDelegationConnectionlessFlow;
228     }
229 
scheduleHandwritingDelegationTimeout()230     private void scheduleHandwritingDelegationTimeout() {
231         if (mDelegationIdleTimeoutHandler == null) {
232             mDelegationIdleTimeoutHandler = new Handler(mLooper);
233         } else {
234             mDelegationIdleTimeoutHandler.removeCallbacks(mDelegationIdleTimeoutRunnable);
235         }
236         mDelegationIdleTimeoutRunnable =  () -> {
237             Slog.d(TAG, "Stylus handwriting delegation idle timed-out.");
238             clearPendingHandwritingDelegation();
239             if (mHandwritingBuffer != null) {
240                 mHandwritingBuffer.forEach(MotionEvent::recycle);
241                 mHandwritingBuffer.clear();
242                 mHandwritingBuffer.trimToSize();
243                 mHandwritingBuffer.ensureCapacity(getHandwritingBufferSize());
244             }
245         };
246         mDelegationIdleTimeoutHandler.postDelayed(
247                 mDelegationIdleTimeoutRunnable, HANDWRITING_DELEGATION_IDLE_TIMEOUT_MS);
248     }
249 
getHandwritingBufferSize()250     private int getHandwritingBufferSize() {
251         if (mDelegatePackageName != null && mDelegatorPackageName != null) {
252             return LONG_EVENT_BUFFER_SIZE;
253         }
254         return EVENT_BUFFER_SIZE;
255     }
256     /**
257      * Clear any pending handwriting delegation info.
258      */
clearPendingHandwritingDelegation()259     void clearPendingHandwritingDelegation() {
260         if (DEBUG) {
261             Slog.d(TAG, "clearPendingHandwritingDelegation");
262         }
263         if (mDelegationIdleTimeoutHandler != null) {
264             mDelegationIdleTimeoutHandler.removeCallbacks(mDelegationIdleTimeoutRunnable);
265             mDelegationIdleTimeoutHandler = null;
266         }
267         mDelegationIdleTimeoutRunnable = null;
268         mDelegatorPackageName = null;
269         mDelegatePackageName = null;
270         mDelegatorFromDefaultHomePackage = false;
271         if (mDelegationConnectionlessFlow) {
272             mDelegationConnectionlessFlow = false;
273             mDiscardDelegationTextRunnable.run();
274         }
275     }
276 
277     /**
278      * Starts a {@link HandwritingSession} to transfer to the IME.
279      *
280      * This must be called from the UI Thread to avoid race conditions between processing more
281      * input events and disposing the input event receiver.
282      * @return the handwriting session to send to the IME, or null if the request was invalid.
283      */
284     @RequiresPermission(Manifest.permission.MONITOR_INPUT)
285     @UiThread
286     @Nullable
startHandwritingSession( int requestId, int imePid, int imeUid, IBinder focusedWindowToken)287     HandwritingSession startHandwritingSession(
288             int requestId, int imePid, int imeUid, IBinder focusedWindowToken) {
289         clearPendingHandwritingDelegation();
290         if (mHandwritingSurface == null) {
291             Slog.e(TAG, "Cannot start handwriting session: Handwriting was not initialized.");
292             return null;
293         }
294         if (requestId != mCurrentRequestId) {
295             Slog.e(TAG, "Cannot start handwriting session: Invalid request id: " + requestId);
296             return null;
297         }
298         if (!isStylusGestureOngoing()) {
299             Slog.e(TAG, "Cannot start handwriting session: No stylus gesture is being recorded.");
300             return null;
301         }
302         Objects.requireNonNull(mHandwritingEventReceiver,
303                 "Handwriting session was already transferred to IME.");
304         final MotionEvent downEvent = mHandwritingBuffer.get(0);
305         assert (downEvent.getActionMasked() == MotionEvent.ACTION_DOWN);
306         if (!mWindowManagerInternal.isPointInsideWindow(
307                 focusedWindowToken, mCurrentDisplayId, downEvent.getRawX(), downEvent.getRawY())) {
308             Slog.e(TAG, "Cannot start handwriting session: "
309                     + "Stylus gesture did not start inside the focused window.");
310             return null;
311         }
312         if (DEBUG) Slog.d(TAG, "Starting handwriting session in display: " + mCurrentDisplayId);
313 
314         InputManagerGlobal.getInstance()
315                 .pilferPointers(mHandwritingSurface.getInputChannel().getToken());
316 
317         // Stop processing more events.
318         mHandwritingEventReceiver.dispose();
319         mHandwritingEventReceiver = null;
320         mRecordingGesture = false;
321         mRecordingGestureAfterStylusUp = false;
322 
323         if (mHandwritingSurface.isIntercepting()) {
324             throw new IllegalStateException(
325                     "Handwriting surface should not be already intercepting.");
326         }
327         mHandwritingSurface.startIntercepting(imePid, imeUid);
328 
329         // Unset the pointer icon for the stylus in case the app had set it.
330         Objects.requireNonNull(mContext.getSystemService(InputManager.class)).setPointerIcon(
331                 PointerIcon.getSystemIcon(mContext, PointerIcon.TYPE_NOT_SPECIFIED),
332                 downEvent.getDisplayId(), downEvent.getDeviceId(), downEvent.getPointerId(0),
333                 mHandwritingSurface.getInputChannel().getToken());
334 
335         return new HandwritingSession(mCurrentRequestId, mHandwritingSurface.getInputChannel(),
336                 mHandwritingBuffer);
337     }
338 
339     /**
340      * Reset the current handwriting session without initializing another session.
341      *
342      * This must be called from UI Thread to avoid race conditions between processing more input
343      * events and disposing the input event receiver.
344      */
345     @UiThread
reset()346     void reset() {
347         reset(false /* reinitializing */);
348     }
349 
setInkWindowInitializer(Runnable inkWindowInitializer)350     void setInkWindowInitializer(Runnable inkWindowInitializer) {
351         mInkWindowInitRunnable = inkWindowInitializer;
352     }
353 
reset(boolean reinitializing)354     private void reset(boolean reinitializing) {
355         if (mHandwritingEventReceiver != null) {
356             mHandwritingEventReceiver.dispose();
357             mHandwritingEventReceiver = null;
358         }
359 
360         if (mHandwritingBuffer != null) {
361             mHandwritingBuffer.forEach(MotionEvent::recycle);
362             mHandwritingBuffer.clear();
363             if (!reinitializing) {
364                 mHandwritingBuffer = null;
365             }
366         }
367 
368         if (mHandwritingSurface != null) {
369             mHandwritingSurface.getInputChannel().dispose();
370             if (!reinitializing) {
371                 mHandwritingSurface.remove();
372                 mHandwritingSurface = null;
373             }
374         }
375 
376         if (!mDelegationConnectionlessFlow) {
377             clearPendingHandwritingDelegation();
378         }
379         mRecordingGesture = false;
380         mRecordingGestureAfterStylusUp = false;
381     }
382 
onInputEvent(InputEvent ev)383     private boolean onInputEvent(InputEvent ev) {
384         if (mHandwritingEventReceiver == null) {
385             throw new IllegalStateException(
386                     "Input Event should not be processed when IME has the spy channel.");
387         }
388 
389         if (!(ev instanceof MotionEvent event)) {
390             Slog.wtf(TAG, "Received non-motion event in stylus monitor.");
391             return false;
392         }
393         if (!event.isStylusPointer()) {
394             return false;
395         }
396         if (event.getDisplayId() != mCurrentDisplayId) {
397             Slog.wtf(TAG, "Received stylus event associated with the incorrect display.");
398             return false;
399         }
400 
401         onStylusEvent(event);
402         return true;
403     }
404 
onStylusEvent(MotionEvent event)405     private void onStylusEvent(MotionEvent event) {
406         final int action = event.getActionMasked();
407 
408         if (mInkWindowInitRunnable != null && (action == MotionEvent.ACTION_HOVER_ENTER
409                 || event.getAction() == MotionEvent.ACTION_HOVER_ENTER)) {
410             // Ask IMMS to make ink window ready.
411             mInkWindowInitRunnable.run();
412             mInkWindowInitRunnable = null;
413             return;
414         } else if (event.isHoverEvent()) {
415             // Hover events need not be recorded to buffer.
416             return;
417         }
418 
419         // If handwriting delegation is ongoing, don't clear the buffer so that multiple strokes
420         // can be buffered across windows.
421         // (This isn't needed for the connectionless delegation flow.)
422         if ((TextUtils.isEmpty(mDelegatePackageName) || mDelegationConnectionlessFlow)
423                 && (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL)) {
424             mRecordingGesture = false;
425             if (handwritingEndOfLineTap() && action == MotionEvent.ACTION_UP) {
426                 mRecordingGestureAfterStylusUp = true;
427             } else {
428                 mHandwritingBuffer.clear();
429                 return;
430             }
431         }
432 
433         if (action == MotionEvent.ACTION_DOWN) {
434             clearBufferIfRecordingAfterStylusUp();
435             mRecordingGesture = true;
436         }
437 
438         if (!mRecordingGesture && !mRecordingGestureAfterStylusUp) {
439             return;
440         }
441 
442         if (mHandwritingBuffer.size() >= getHandwritingBufferSize()) {
443             if (DEBUG) {
444                 Slog.w(TAG, "Current gesture exceeds the buffer capacity."
445                         + " The rest of the gesture will not be recorded.");
446             }
447             mRecordingGesture = false;
448             clearBufferIfRecordingAfterStylusUp();
449             return;
450         }
451 
452         mHandwritingBuffer.add(MotionEvent.obtain(event));
453     }
454 
clearBufferIfRecordingAfterStylusUp()455     private void clearBufferIfRecordingAfterStylusUp() {
456         if (mRecordingGestureAfterStylusUp) {
457             mHandwritingBuffer.clear();
458             mRecordingGestureAfterStylusUp = false;
459         }
460     }
461 
462     static final class HandwritingSession {
463         private final int mRequestId;
464         private final InputChannel mHandwritingChannel;
465         private final List<MotionEvent> mRecordedEvents;
466 
HandwritingSession(int requestId, InputChannel handwritingChannel, List<MotionEvent> recordedEvents)467         private HandwritingSession(int requestId, InputChannel handwritingChannel,
468                 List<MotionEvent> recordedEvents) {
469             mRequestId = requestId;
470             mHandwritingChannel = handwritingChannel;
471             mRecordedEvents = recordedEvents;
472         }
473 
getRequestId()474         int getRequestId() {
475             return mRequestId;
476         }
477 
getHandwritingChannel()478         InputChannel getHandwritingChannel() {
479             return mHandwritingChannel;
480         }
481 
getRecordedEvents()482         List<MotionEvent> getRecordedEvents() {
483             return mRecordedEvents;
484         }
485     }
486 }
487