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