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