1 /* 2 * Copyright (C) 2021 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.companion.virtual; 18 19 import static android.text.TextUtils.formatSimple; 20 21 import android.annotation.IntDef; 22 import android.annotation.NonNull; 23 import android.annotation.StringDef; 24 import android.content.AttributionSource; 25 import android.graphics.PointF; 26 import android.hardware.display.DisplayManagerInternal; 27 import android.hardware.input.InputDeviceIdentifier; 28 import android.hardware.input.InputManager; 29 import android.hardware.input.InputManagerGlobal; 30 import android.hardware.input.VirtualKeyEvent; 31 import android.hardware.input.VirtualMouseButtonEvent; 32 import android.hardware.input.VirtualMouseRelativeEvent; 33 import android.hardware.input.VirtualMouseScrollEvent; 34 import android.hardware.input.VirtualStylusButtonEvent; 35 import android.hardware.input.VirtualStylusMotionEvent; 36 import android.hardware.input.VirtualTouchEvent; 37 import android.os.Handler; 38 import android.os.IBinder; 39 import android.os.IInputConstants; 40 import android.os.RemoteException; 41 import android.util.ArrayMap; 42 import android.util.Log; 43 import android.util.Slog; 44 import android.view.InputDevice; 45 import android.view.WindowManager; 46 47 import com.android.internal.annotations.GuardedBy; 48 import com.android.internal.annotations.VisibleForTesting; 49 import com.android.modules.expresslog.Counter; 50 import com.android.server.LocalServices; 51 import com.android.server.input.InputManagerInternal; 52 53 import java.io.PrintWriter; 54 import java.lang.annotation.Retention; 55 import java.lang.annotation.RetentionPolicy; 56 import java.util.Iterator; 57 import java.util.Map; 58 import java.util.Objects; 59 import java.util.concurrent.CountDownLatch; 60 import java.util.concurrent.TimeUnit; 61 import java.util.concurrent.atomic.AtomicLong; 62 import java.util.function.Supplier; 63 64 /** Controls virtual input devices, including device lifecycle and event dispatch. */ 65 class InputController { 66 67 private static final String TAG = "VirtualInputController"; 68 69 private static final AtomicLong sNextPhysId = new AtomicLong(1); 70 71 static final String NAVIGATION_TOUCHPAD_DEVICE_TYPE = "touchNavigation"; 72 73 static final String PHYS_TYPE_DPAD = "Dpad"; 74 static final String PHYS_TYPE_KEYBOARD = "Keyboard"; 75 static final String PHYS_TYPE_MOUSE = "Mouse"; 76 static final String PHYS_TYPE_TOUCHSCREEN = "Touchscreen"; 77 static final String PHYS_TYPE_NAVIGATION_TOUCHPAD = "NavigationTouchpad"; 78 static final String PHYS_TYPE_STYLUS = "Stylus"; 79 @StringDef(prefix = { "PHYS_TYPE_" }, value = { 80 PHYS_TYPE_DPAD, 81 PHYS_TYPE_KEYBOARD, 82 PHYS_TYPE_MOUSE, 83 PHYS_TYPE_TOUCHSCREEN, 84 PHYS_TYPE_NAVIGATION_TOUCHPAD, 85 PHYS_TYPE_STYLUS, 86 }) 87 @Retention(RetentionPolicy.SOURCE) 88 @interface PhysType { 89 } 90 91 final Object mLock = new Object(); 92 93 /* Token -> file descriptor associations. */ 94 @GuardedBy("mLock") 95 private final ArrayMap<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = 96 new ArrayMap<>(); 97 98 private final Handler mHandler; 99 private final NativeWrapper mNativeWrapper; 100 private final DisplayManagerInternal mDisplayManagerInternal; 101 private final InputManagerInternal mInputManagerInternal; 102 private final WindowManager mWindowManager; 103 private final AttributionSource mAttributionSource; 104 private final DeviceCreationThreadVerifier mThreadVerifier; 105 InputController(@onNull Handler handler, @NonNull WindowManager windowManager, AttributionSource attributionSource)106 InputController(@NonNull Handler handler, 107 @NonNull WindowManager windowManager, AttributionSource attributionSource) { 108 this(new NativeWrapper(), handler, windowManager, attributionSource, 109 // Verify that virtual devices are not created on the handler thread. 110 () -> !handler.getLooper().isCurrentThread()); 111 } 112 113 @VisibleForTesting InputController(@onNull NativeWrapper nativeWrapper, @NonNull Handler handler, @NonNull WindowManager windowManager, AttributionSource attributionSource, @NonNull DeviceCreationThreadVerifier threadVerifier)114 InputController(@NonNull NativeWrapper nativeWrapper, 115 @NonNull Handler handler, @NonNull WindowManager windowManager, 116 AttributionSource attributionSource, 117 @NonNull DeviceCreationThreadVerifier threadVerifier) { 118 mHandler = handler; 119 mNativeWrapper = nativeWrapper; 120 mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); 121 mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); 122 mWindowManager = windowManager; 123 mAttributionSource = attributionSource; 124 mThreadVerifier = threadVerifier; 125 } 126 close()127 void close() { 128 synchronized (mLock) { 129 final Iterator<Map.Entry<IBinder, InputDeviceDescriptor>> iterator = 130 mInputDeviceDescriptors.entrySet().iterator(); 131 if (iterator.hasNext()) { 132 final Map.Entry<IBinder, InputDeviceDescriptor> entry = iterator.next(); 133 final IBinder token = entry.getKey(); 134 final InputDeviceDescriptor inputDeviceDescriptor = entry.getValue(); 135 iterator.remove(); 136 closeInputDeviceDescriptorLocked(token, inputDeviceDescriptor); 137 } 138 } 139 } 140 createDpad(@onNull String deviceName, int vendorId, int productId, @NonNull IBinder deviceToken, int displayId)141 void createDpad(@NonNull String deviceName, int vendorId, int productId, 142 @NonNull IBinder deviceToken, int displayId) throws DeviceCreationException { 143 final String phys = createPhys(PHYS_TYPE_DPAD); 144 createDeviceInternal(InputDeviceDescriptor.TYPE_DPAD, deviceName, vendorId, 145 productId, deviceToken, displayId, phys, 146 () -> mNativeWrapper.openUinputDpad(deviceName, vendorId, productId, phys)); 147 } 148 createKeyboard(@onNull String deviceName, int vendorId, int productId, @NonNull IBinder deviceToken, int displayId, @NonNull String languageTag, @NonNull String layoutType)149 void createKeyboard(@NonNull String deviceName, int vendorId, int productId, 150 @NonNull IBinder deviceToken, int displayId, @NonNull String languageTag, 151 @NonNull String layoutType) throws DeviceCreationException { 152 final String phys = createPhys(PHYS_TYPE_KEYBOARD); 153 mInputManagerInternal.addKeyboardLayoutAssociation(phys, languageTag, 154 layoutType); 155 try { 156 createDeviceInternal(InputDeviceDescriptor.TYPE_KEYBOARD, deviceName, vendorId, 157 productId, deviceToken, displayId, phys, 158 () -> mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys)); 159 } catch (DeviceCreationException e) { 160 mInputManagerInternal.removeKeyboardLayoutAssociation(phys); 161 throw e; 162 } 163 } 164 createMouse(@onNull String deviceName, int vendorId, int productId, @NonNull IBinder deviceToken, int displayId)165 void createMouse(@NonNull String deviceName, int vendorId, int productId, 166 @NonNull IBinder deviceToken, int displayId) throws DeviceCreationException { 167 final String phys = createPhys(PHYS_TYPE_MOUSE); 168 createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId, 169 deviceToken, displayId, phys, 170 () -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys)); 171 } 172 createTouchscreen(@onNull String deviceName, int vendorId, int productId, @NonNull IBinder deviceToken, int displayId, int height, int width)173 void createTouchscreen(@NonNull String deviceName, int vendorId, int productId, 174 @NonNull IBinder deviceToken, int displayId, int height, int width) 175 throws DeviceCreationException { 176 final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN); 177 createDeviceInternal(InputDeviceDescriptor.TYPE_TOUCHSCREEN, deviceName, vendorId, 178 productId, deviceToken, displayId, phys, 179 () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, phys, 180 height, width)); 181 } 182 createNavigationTouchpad(@onNull String deviceName, int vendorId, int productId, @NonNull IBinder deviceToken, int displayId, int height, int width)183 void createNavigationTouchpad(@NonNull String deviceName, int vendorId, int productId, 184 @NonNull IBinder deviceToken, int displayId, int height, int width) 185 throws DeviceCreationException { 186 final String phys = createPhys(PHYS_TYPE_NAVIGATION_TOUCHPAD); 187 mInputManagerInternal.setTypeAssociation(phys, NAVIGATION_TOUCHPAD_DEVICE_TYPE); 188 try { 189 createDeviceInternal(InputDeviceDescriptor.TYPE_NAVIGATION_TOUCHPAD, deviceName, 190 vendorId, productId, deviceToken, displayId, phys, 191 () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId, 192 phys, height, width)); 193 } catch (DeviceCreationException e) { 194 mInputManagerInternal.unsetTypeAssociation(phys); 195 throw e; 196 } 197 } 198 createStylus(@onNull String deviceName, int vendorId, int productId, @NonNull IBinder deviceToken, int displayId, int height, int width)199 void createStylus(@NonNull String deviceName, int vendorId, int productId, 200 @NonNull IBinder deviceToken, int displayId, int height, int width) 201 throws DeviceCreationException { 202 final String phys = createPhys(PHYS_TYPE_STYLUS); 203 createDeviceInternal(InputDeviceDescriptor.TYPE_STYLUS, deviceName, vendorId, 204 productId, deviceToken, displayId, phys, 205 () -> mNativeWrapper.openUinputStylus(deviceName, vendorId, productId, phys, 206 height, width)); 207 } 208 unregisterInputDevice(@onNull IBinder token)209 void unregisterInputDevice(@NonNull IBinder token) { 210 synchronized (mLock) { 211 final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.remove( 212 token); 213 if (inputDeviceDescriptor == null) { 214 Slog.w(TAG, "Could not unregister input device for given token."); 215 } else { 216 closeInputDeviceDescriptorLocked(token, inputDeviceDescriptor); 217 } 218 } 219 } 220 221 @GuardedBy("mLock") closeInputDeviceDescriptorLocked(IBinder token, InputDeviceDescriptor inputDeviceDescriptor)222 private void closeInputDeviceDescriptorLocked(IBinder token, 223 InputDeviceDescriptor inputDeviceDescriptor) { 224 token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0); 225 mNativeWrapper.closeUinput(inputDeviceDescriptor.getNativePointer()); 226 String phys = inputDeviceDescriptor.getPhys(); 227 InputManagerGlobal.getInstance().removeUniqueIdAssociationByPort(phys); 228 // Type associations are added in the case of navigation touchpads. Those should be removed 229 // once the input device gets closed. 230 if (inputDeviceDescriptor.getType() == InputDeviceDescriptor.TYPE_NAVIGATION_TOUCHPAD) { 231 mInputManagerInternal.unsetTypeAssociation(phys); 232 } 233 234 if (inputDeviceDescriptor.getType() == InputDeviceDescriptor.TYPE_KEYBOARD) { 235 mInputManagerInternal.removeKeyboardLayoutAssociation(phys); 236 } 237 } 238 239 /** 240 * @return the device id for a given token (identifiying a device) 241 */ getInputDeviceId(IBinder token)242 int getInputDeviceId(IBinder token) { 243 synchronized (mLock) { 244 final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(token); 245 if (inputDeviceDescriptor == null) { 246 throw new IllegalArgumentException("Could not get device id for given token"); 247 } 248 return inputDeviceDescriptor.getInputDeviceId(); 249 } 250 } 251 setShowPointerIcon(boolean visible, int displayId)252 void setShowPointerIcon(boolean visible, int displayId) { 253 mInputManagerInternal.setPointerIconVisible(visible, displayId); 254 } 255 setMousePointerAccelerationEnabled(boolean enabled, int displayId)256 void setMousePointerAccelerationEnabled(boolean enabled, int displayId) { 257 mInputManagerInternal.setMousePointerAccelerationEnabled(enabled, displayId); 258 } 259 setDisplayEligibilityForPointerCapture(boolean isEligible, int displayId)260 void setDisplayEligibilityForPointerCapture(boolean isEligible, int displayId) { 261 mInputManagerInternal.setDisplayEligibilityForPointerCapture(displayId, isEligible); 262 } 263 setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy)264 void setDisplayImePolicy(int displayId, @WindowManager.DisplayImePolicy int policy) { 265 mWindowManager.setDisplayImePolicy(displayId, policy); 266 } 267 268 /** 269 * Validates a device name by checking whether a device with the same name already exists. 270 * @param deviceName The name of the device to be validated 271 * @throws DeviceCreationException if {@code deviceName} is not valid. 272 */ validateDeviceName(String deviceName)273 private void validateDeviceName(String deviceName) throws DeviceCreationException { 274 synchronized (mLock) { 275 for (int i = 0; i < mInputDeviceDescriptors.size(); ++i) { 276 if (mInputDeviceDescriptors.valueAt(i).mName.equals(deviceName)) { 277 throw new DeviceCreationException( 278 "Input device name already in use: " + deviceName); 279 } 280 } 281 } 282 } 283 createPhys(@hysType String type)284 private static String createPhys(@PhysType String type) { 285 return formatSimple("virtual%s:%d", type, sNextPhysId.getAndIncrement()); 286 } 287 setUniqueIdAssociation(int displayId, String phys)288 private void setUniqueIdAssociation(int displayId, String phys) { 289 final String displayUniqueId = mDisplayManagerInternal.getDisplayInfo(displayId).uniqueId; 290 InputManagerGlobal.getInstance().addUniqueIdAssociationByPort(phys, displayUniqueId); 291 } 292 sendDpadKeyEvent(@onNull IBinder token, @NonNull VirtualKeyEvent event)293 boolean sendDpadKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) { 294 synchronized (mLock) { 295 final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( 296 token); 297 if (inputDeviceDescriptor == null) { 298 return false; 299 } 300 return mNativeWrapper.writeDpadKeyEvent(inputDeviceDescriptor.getNativePointer(), 301 event.getKeyCode(), event.getAction(), event.getEventTimeNanos()); 302 } 303 } 304 sendKeyEvent(@onNull IBinder token, @NonNull VirtualKeyEvent event)305 boolean sendKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) { 306 synchronized (mLock) { 307 final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( 308 token); 309 if (inputDeviceDescriptor == null) { 310 return false; 311 } 312 return mNativeWrapper.writeKeyEvent(inputDeviceDescriptor.getNativePointer(), 313 event.getKeyCode(), event.getAction(), event.getEventTimeNanos()); 314 } 315 } 316 sendButtonEvent(@onNull IBinder token, @NonNull VirtualMouseButtonEvent event)317 boolean sendButtonEvent(@NonNull IBinder token, @NonNull VirtualMouseButtonEvent event) { 318 synchronized (mLock) { 319 final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( 320 token); 321 if (inputDeviceDescriptor == null) { 322 return false; 323 } 324 return mNativeWrapper.writeButtonEvent(inputDeviceDescriptor.getNativePointer(), 325 event.getButtonCode(), event.getAction(), event.getEventTimeNanos()); 326 } 327 } 328 sendTouchEvent(@onNull IBinder token, @NonNull VirtualTouchEvent event)329 boolean sendTouchEvent(@NonNull IBinder token, @NonNull VirtualTouchEvent event) { 330 synchronized (mLock) { 331 final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( 332 token); 333 if (inputDeviceDescriptor == null) { 334 return false; 335 } 336 return mNativeWrapper.writeTouchEvent(inputDeviceDescriptor.getNativePointer(), 337 event.getPointerId(), event.getToolType(), event.getAction(), event.getX(), 338 event.getY(), event.getPressure(), event.getMajorAxisSize(), 339 event.getEventTimeNanos()); 340 } 341 } 342 sendRelativeEvent(@onNull IBinder token, @NonNull VirtualMouseRelativeEvent event)343 boolean sendRelativeEvent(@NonNull IBinder token, @NonNull VirtualMouseRelativeEvent event) { 344 synchronized (mLock) { 345 final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( 346 token); 347 if (inputDeviceDescriptor == null) { 348 return false; 349 } 350 return mNativeWrapper.writeRelativeEvent(inputDeviceDescriptor.getNativePointer(), 351 event.getRelativeX(), event.getRelativeY(), event.getEventTimeNanos()); 352 } 353 } 354 sendScrollEvent(@onNull IBinder token, @NonNull VirtualMouseScrollEvent event)355 boolean sendScrollEvent(@NonNull IBinder token, @NonNull VirtualMouseScrollEvent event) { 356 synchronized (mLock) { 357 final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( 358 token); 359 if (inputDeviceDescriptor == null) { 360 return false; 361 } 362 return mNativeWrapper.writeScrollEvent(inputDeviceDescriptor.getNativePointer(), 363 event.getXAxisMovement(), event.getYAxisMovement(), event.getEventTimeNanos()); 364 } 365 } 366 getCursorPosition(@onNull IBinder token)367 public PointF getCursorPosition(@NonNull IBinder token) { 368 synchronized (mLock) { 369 final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( 370 token); 371 if (inputDeviceDescriptor == null) { 372 throw new IllegalArgumentException( 373 "Could not get cursor position for input device for given token"); 374 } 375 return LocalServices.getService(InputManagerInternal.class).getCursorPosition( 376 inputDeviceDescriptor.getDisplayId()); 377 } 378 } 379 sendStylusMotionEvent(@onNull IBinder token, @NonNull VirtualStylusMotionEvent event)380 boolean sendStylusMotionEvent(@NonNull IBinder token, @NonNull VirtualStylusMotionEvent event) { 381 synchronized (mLock) { 382 final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( 383 token); 384 if (inputDeviceDescriptor == null) { 385 return false; 386 } 387 return mNativeWrapper.writeStylusMotionEvent(inputDeviceDescriptor.getNativePointer(), 388 event.getToolType(), event.getAction(), event.getX(), event.getY(), 389 event.getPressure(), event.getTiltX(), event.getTiltY(), 390 event.getEventTimeNanos()); 391 } 392 } 393 sendStylusButtonEvent(@onNull IBinder token, @NonNull VirtualStylusButtonEvent event)394 boolean sendStylusButtonEvent(@NonNull IBinder token, @NonNull VirtualStylusButtonEvent event) { 395 synchronized (mLock) { 396 final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get( 397 token); 398 if (inputDeviceDescriptor == null) { 399 return false; 400 } 401 return mNativeWrapper.writeStylusButtonEvent(inputDeviceDescriptor.getNativePointer(), 402 event.getButtonCode(), event.getAction(), event.getEventTimeNanos()); 403 } 404 } 405 dump(@onNull PrintWriter fout)406 public void dump(@NonNull PrintWriter fout) { 407 fout.println(" InputController: "); 408 synchronized (mLock) { 409 fout.println(" Active descriptors: "); 410 for (int i = 0; i < mInputDeviceDescriptors.size(); ++i) { 411 InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.valueAt(i); 412 fout.println(" ptr: " + inputDeviceDescriptor.getNativePointer()); 413 fout.println(" displayId: " + inputDeviceDescriptor.getDisplayId()); 414 fout.println(" creationOrder: " 415 + inputDeviceDescriptor.getCreationOrderNumber()); 416 fout.println(" type: " + inputDeviceDescriptor.getType()); 417 fout.println(" phys: " + inputDeviceDescriptor.getPhys()); 418 fout.println( 419 " inputDeviceId: " + inputDeviceDescriptor.getInputDeviceId()); 420 } 421 } 422 } 423 424 @VisibleForTesting addDeviceForTesting(IBinder deviceToken, long ptr, int type, int displayId, String phys, String deviceName, int inputDeviceId)425 void addDeviceForTesting(IBinder deviceToken, long ptr, int type, int displayId, String phys, 426 String deviceName, int inputDeviceId) { 427 synchronized (mLock) { 428 mInputDeviceDescriptors.put(deviceToken, new InputDeviceDescriptor(ptr, () -> { 429 }, type, displayId, phys, deviceName, inputDeviceId)); 430 } 431 } 432 433 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) getInputDeviceDescriptors()434 Map<IBinder, InputDeviceDescriptor> getInputDeviceDescriptors() { 435 final Map<IBinder, InputDeviceDescriptor> inputDeviceDescriptors = new ArrayMap<>(); 436 synchronized (mLock) { 437 inputDeviceDescriptors.putAll(mInputDeviceDescriptors); 438 } 439 return inputDeviceDescriptors; 440 } 441 nativeOpenUinputDpad(String deviceName, int vendorId, int productId, String phys)442 private static native long nativeOpenUinputDpad(String deviceName, int vendorId, int productId, 443 String phys); nativeOpenUinputKeyboard(String deviceName, int vendorId, int productId, String phys)444 private static native long nativeOpenUinputKeyboard(String deviceName, int vendorId, 445 int productId, String phys); nativeOpenUinputMouse(String deviceName, int vendorId, int productId, String phys)446 private static native long nativeOpenUinputMouse(String deviceName, int vendorId, int productId, 447 String phys); nativeOpenUinputTouchscreen(String deviceName, int vendorId, int productId, String phys, int height, int width)448 private static native long nativeOpenUinputTouchscreen(String deviceName, int vendorId, 449 int productId, String phys, int height, int width); nativeOpenUinputStylus(String deviceName, int vendorId, int productId, String phys, int height, int width)450 private static native long nativeOpenUinputStylus(String deviceName, int vendorId, 451 int productId, String phys, int height, int width); nativeCloseUinput(long ptr)452 private static native void nativeCloseUinput(long ptr); nativeWriteDpadKeyEvent(long ptr, int androidKeyCode, int action, long eventTimeNanos)453 private static native boolean nativeWriteDpadKeyEvent(long ptr, int androidKeyCode, int action, 454 long eventTimeNanos); nativeWriteKeyEvent(long ptr, int androidKeyCode, int action, long eventTimeNanos)455 private static native boolean nativeWriteKeyEvent(long ptr, int androidKeyCode, int action, 456 long eventTimeNanos); nativeWriteButtonEvent(long ptr, int buttonCode, int action, long eventTimeNanos)457 private static native boolean nativeWriteButtonEvent(long ptr, int buttonCode, int action, 458 long eventTimeNanos); nativeWriteTouchEvent(long ptr, int pointerId, int toolType, int action, float locationX, float locationY, float pressure, float majorAxisSize, long eventTimeNanos)459 private static native boolean nativeWriteTouchEvent(long ptr, int pointerId, int toolType, 460 int action, float locationX, float locationY, float pressure, float majorAxisSize, 461 long eventTimeNanos); nativeWriteRelativeEvent(long ptr, float relativeX, float relativeY, long eventTimeNanos)462 private static native boolean nativeWriteRelativeEvent(long ptr, float relativeX, 463 float relativeY, long eventTimeNanos); nativeWriteScrollEvent(long ptr, float xAxisMovement, float yAxisMovement, long eventTimeNanos)464 private static native boolean nativeWriteScrollEvent(long ptr, float xAxisMovement, 465 float yAxisMovement, long eventTimeNanos); nativeWriteStylusMotionEvent(long ptr, int toolType, int action, int locationX, int locationY, int pressure, int tiltX, int tiltY, long eventTimeNanos)466 private static native boolean nativeWriteStylusMotionEvent(long ptr, int toolType, int action, 467 int locationX, int locationY, int pressure, int tiltX, int tiltY, long eventTimeNanos); nativeWriteStylusButtonEvent(long ptr, int buttonCode, int action, long eventTimeNanos)468 private static native boolean nativeWriteStylusButtonEvent(long ptr, int buttonCode, int action, 469 long eventTimeNanos); 470 471 /** Wrapper around the static native methods for tests. */ 472 @VisibleForTesting 473 protected static class NativeWrapper { openUinputDpad(String deviceName, int vendorId, int productId, String phys)474 public long openUinputDpad(String deviceName, int vendorId, int productId, String phys) { 475 return nativeOpenUinputDpad(deviceName, vendorId, productId, phys); 476 } 477 openUinputKeyboard(String deviceName, int vendorId, int productId, String phys)478 public long openUinputKeyboard(String deviceName, int vendorId, int productId, 479 String phys) { 480 return nativeOpenUinputKeyboard(deviceName, vendorId, productId, phys); 481 } 482 openUinputMouse(String deviceName, int vendorId, int productId, String phys)483 public long openUinputMouse(String deviceName, int vendorId, int productId, String phys) { 484 return nativeOpenUinputMouse(deviceName, vendorId, productId, phys); 485 } 486 openUinputTouchscreen(String deviceName, int vendorId, int productId, String phys, int height, int width)487 public long openUinputTouchscreen(String deviceName, int vendorId, 488 int productId, String phys, int height, int width) { 489 return nativeOpenUinputTouchscreen(deviceName, vendorId, productId, phys, height, 490 width); 491 } 492 openUinputStylus(String deviceName, int vendorId, int productId, String phys, int height, int width)493 public long openUinputStylus(String deviceName, int vendorId, int productId, String phys, 494 int height, int width) { 495 return nativeOpenUinputStylus(deviceName, vendorId, productId, phys, height, width); 496 } 497 closeUinput(long ptr)498 public void closeUinput(long ptr) { 499 nativeCloseUinput(ptr); 500 } 501 writeDpadKeyEvent(long ptr, int androidKeyCode, int action, long eventTimeNanos)502 public boolean writeDpadKeyEvent(long ptr, int androidKeyCode, int action, 503 long eventTimeNanos) { 504 return nativeWriteDpadKeyEvent(ptr, androidKeyCode, action, eventTimeNanos); 505 } 506 writeKeyEvent(long ptr, int androidKeyCode, int action, long eventTimeNanos)507 public boolean writeKeyEvent(long ptr, int androidKeyCode, int action, 508 long eventTimeNanos) { 509 return nativeWriteKeyEvent(ptr, androidKeyCode, action, eventTimeNanos); 510 } 511 writeButtonEvent(long ptr, int buttonCode, int action, long eventTimeNanos)512 public boolean writeButtonEvent(long ptr, int buttonCode, int action, 513 long eventTimeNanos) { 514 return nativeWriteButtonEvent(ptr, buttonCode, action, eventTimeNanos); 515 } 516 writeTouchEvent(long ptr, int pointerId, int toolType, int action, float locationX, float locationY, float pressure, float majorAxisSize, long eventTimeNanos)517 public boolean writeTouchEvent(long ptr, int pointerId, int toolType, int action, 518 float locationX, float locationY, float pressure, float majorAxisSize, 519 long eventTimeNanos) { 520 return nativeWriteTouchEvent(ptr, pointerId, toolType, 521 action, locationX, locationY, 522 pressure, majorAxisSize, eventTimeNanos); 523 } 524 writeRelativeEvent(long ptr, float relativeX, float relativeY, long eventTimeNanos)525 public boolean writeRelativeEvent(long ptr, float relativeX, float relativeY, 526 long eventTimeNanos) { 527 return nativeWriteRelativeEvent(ptr, relativeX, relativeY, eventTimeNanos); 528 } 529 writeScrollEvent(long ptr, float xAxisMovement, float yAxisMovement, long eventTimeNanos)530 public boolean writeScrollEvent(long ptr, float xAxisMovement, float yAxisMovement, 531 long eventTimeNanos) { 532 return nativeWriteScrollEvent(ptr, xAxisMovement, yAxisMovement, eventTimeNanos); 533 } 534 writeStylusMotionEvent(long ptr, int toolType, int action, int locationX, int locationY, int pressure, int tiltX, int tiltY, long eventTimeNanos)535 public boolean writeStylusMotionEvent(long ptr, int toolType, int action, int locationX, 536 int locationY, int pressure, int tiltX, int tiltY, long eventTimeNanos) { 537 return nativeWriteStylusMotionEvent(ptr, toolType, action, locationX, locationY, 538 pressure, tiltX, tiltY, eventTimeNanos); 539 } 540 writeStylusButtonEvent(long ptr, int buttonCode, int action, long eventTimeNanos)541 public boolean writeStylusButtonEvent(long ptr, int buttonCode, int action, 542 long eventTimeNanos) { 543 return nativeWriteStylusButtonEvent(ptr, buttonCode, action, eventTimeNanos); 544 } 545 } 546 547 @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) 548 static final class InputDeviceDescriptor { 549 550 static final int TYPE_KEYBOARD = 1; 551 static final int TYPE_MOUSE = 2; 552 static final int TYPE_TOUCHSCREEN = 3; 553 static final int TYPE_DPAD = 4; 554 static final int TYPE_NAVIGATION_TOUCHPAD = 5; 555 static final int TYPE_STYLUS = 6; 556 @IntDef(prefix = { "TYPE_" }, value = { 557 TYPE_KEYBOARD, 558 TYPE_MOUSE, 559 TYPE_TOUCHSCREEN, 560 TYPE_DPAD, 561 TYPE_NAVIGATION_TOUCHPAD, 562 TYPE_STYLUS, 563 }) 564 @Retention(RetentionPolicy.SOURCE) 565 @interface Type { 566 } 567 568 private static final AtomicLong sNextCreationOrderNumber = new AtomicLong(1); 569 570 // Pointer to the native input device object. 571 private final long mPtr; 572 private final IBinder.DeathRecipient mDeathRecipient; 573 private final @Type int mType; 574 private final int mDisplayId; 575 private final String mPhys; 576 // The name given to this device by the client. Enforced to be unique within 577 // InputController. 578 private final String mName; 579 // The input device id that was associated to the device by the InputReader on device 580 // creation. 581 private final int mInputDeviceId; 582 // Monotonically increasing number; devices with lower numbers were created earlier. 583 private final long mCreationOrderNumber; 584 InputDeviceDescriptor(long ptr, IBinder.DeathRecipient deathRecipient, @Type int type, int displayId, String phys, String name, int inputDeviceId)585 InputDeviceDescriptor(long ptr, IBinder.DeathRecipient deathRecipient, @Type int type, 586 int displayId, String phys, String name, int inputDeviceId) { 587 mPtr = ptr; 588 mDeathRecipient = deathRecipient; 589 mType = type; 590 mDisplayId = displayId; 591 mPhys = phys; 592 mName = name; 593 mInputDeviceId = inputDeviceId; 594 mCreationOrderNumber = sNextCreationOrderNumber.getAndIncrement(); 595 } 596 getNativePointer()597 public long getNativePointer() { 598 return mPtr; 599 } 600 getType()601 public int getType() { 602 return mType; 603 } 604 isMouse()605 public boolean isMouse() { 606 return mType == TYPE_MOUSE; 607 } 608 getDeathRecipient()609 public IBinder.DeathRecipient getDeathRecipient() { 610 return mDeathRecipient; 611 } 612 getDisplayId()613 public int getDisplayId() { 614 return mDisplayId; 615 } 616 getCreationOrderNumber()617 public long getCreationOrderNumber() { 618 return mCreationOrderNumber; 619 } 620 getPhys()621 public String getPhys() { 622 return mPhys; 623 } 624 getInputDeviceId()625 public int getInputDeviceId() { 626 return mInputDeviceId; 627 } 628 } 629 630 private final class BinderDeathRecipient implements IBinder.DeathRecipient { 631 632 private final IBinder mDeviceToken; 633 BinderDeathRecipient(IBinder deviceToken)634 BinderDeathRecipient(IBinder deviceToken) { 635 mDeviceToken = deviceToken; 636 } 637 638 @Override binderDied()639 public void binderDied() { 640 // All callers are expected to call {@link VirtualDevice#unregisterInputDevice} before 641 // quitting, which removes this death recipient. If this is invoked, the remote end 642 // died, or they disposed of the object without properly unregistering. 643 Slog.e(TAG, "Virtual input controller binder died"); 644 unregisterInputDevice(mDeviceToken); 645 } 646 } 647 648 /** A helper class used to wait for an input device to be registered. */ 649 private class WaitForDevice implements AutoCloseable { 650 private final CountDownLatch mDeviceAddedLatch = new CountDownLatch(1); 651 private final InputManager.InputDeviceListener mListener; 652 653 private int mInputDeviceId = IInputConstants.INVALID_INPUT_DEVICE_ID; 654 WaitForDevice(String deviceName, int vendorId, int productId, int associatedDisplayId)655 WaitForDevice(String deviceName, int vendorId, int productId, int associatedDisplayId) { 656 mListener = new InputManager.InputDeviceListener() { 657 @Override 658 public void onInputDeviceAdded(int deviceId) { 659 onInputDeviceChanged(deviceId); 660 } 661 662 @Override 663 public void onInputDeviceRemoved(int deviceId) { 664 } 665 666 @Override 667 public void onInputDeviceChanged(int deviceId) { 668 if (isMatchingDevice(deviceId)) { 669 mInputDeviceId = deviceId; 670 mDeviceAddedLatch.countDown(); 671 } 672 } 673 674 private boolean isMatchingDevice(int deviceId) { 675 final InputDevice device = InputManagerGlobal.getInstance().getInputDevice( 676 deviceId); 677 Objects.requireNonNull(device, "Newly added input device was null."); 678 if (!device.getName().equals(deviceName)) { 679 return false; 680 } 681 final InputDeviceIdentifier id = device.getIdentifier(); 682 if (id.getVendorId() != vendorId || id.getProductId() != productId) { 683 return false; 684 } 685 return device.getAssociatedDisplayId() == associatedDisplayId; 686 } 687 }; 688 InputManagerGlobal.getInstance().registerInputDeviceListener(mListener, mHandler); 689 } 690 691 /** 692 * Note: This must not be called from {@link #mHandler}'s thread. 693 * @throws DeviceCreationException if the device was not created successfully within the 694 * timeout. 695 * @return The id of the created input device. 696 */ waitForDeviceCreation()697 int waitForDeviceCreation() throws DeviceCreationException { 698 try { 699 if (!mDeviceAddedLatch.await(1, TimeUnit.MINUTES)) { 700 throw new DeviceCreationException( 701 "Timed out waiting for virtual device to be created."); 702 } 703 } catch (InterruptedException e) { 704 throw new DeviceCreationException( 705 "Interrupted while waiting for virtual device to be created.", e); 706 } 707 if (mInputDeviceId == IInputConstants.INVALID_INPUT_DEVICE_ID) { 708 throw new IllegalStateException( 709 "Virtual input device was created with an invalid " 710 + "id=" + mInputDeviceId); 711 } 712 return mInputDeviceId; 713 } 714 715 @Override close()716 public void close() { 717 InputManagerGlobal.getInstance().unregisterInputDeviceListener(mListener); 718 } 719 } 720 721 /** An internal exception that is thrown to indicate an error when opening a virtual device. */ 722 static class DeviceCreationException extends Exception { DeviceCreationException()723 DeviceCreationException() { 724 super(); 725 } DeviceCreationException(String message)726 DeviceCreationException(String message) { 727 super(message); 728 } DeviceCreationException(String message, Throwable cause)729 DeviceCreationException(String message, Throwable cause) { 730 super(message, cause); 731 } DeviceCreationException(Throwable cause)732 DeviceCreationException(Throwable cause) { 733 super(cause); 734 } 735 } 736 737 /** 738 * Creates a virtual input device synchronously, and waits for the notification that the device 739 * was added. 740 * 741 * Note: Input device creation is expected to happen on a binder thread, and the calling thread 742 * will be blocked until the input device creation is successful. This should not be called on 743 * the handler's thread. 744 * 745 * @throws DeviceCreationException Throws this exception if anything unexpected happens in the 746 * process of creating the device. This method will take care 747 * to restore the state of the system in the event of any 748 * unexpected behavior. 749 */ createDeviceInternal(@nputDeviceDescriptor.Type int type, String deviceName, int vendorId, int productId, IBinder deviceToken, int displayId, String phys, Supplier<Long> deviceOpener)750 private void createDeviceInternal(@InputDeviceDescriptor.Type int type, String deviceName, 751 int vendorId, int productId, IBinder deviceToken, int displayId, String phys, 752 Supplier<Long> deviceOpener) 753 throws DeviceCreationException { 754 if (!mThreadVerifier.isValidThread()) { 755 throw new IllegalStateException( 756 "Virtual device creation should happen on an auxiliary thread (e.g. binder " 757 + "thread) and not from the handler's thread."); 758 } 759 validateDeviceName(deviceName); 760 761 final long ptr; 762 final BinderDeathRecipient binderDeathRecipient; 763 764 final int inputDeviceId; 765 766 setUniqueIdAssociation(displayId, phys); 767 try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId, displayId)) { 768 ptr = deviceOpener.get(); 769 // See INVALID_PTR in libs/input/VirtualInputDevice.cpp. 770 if (ptr == 0) { 771 throw new DeviceCreationException( 772 "A native error occurred when creating virtual input device: " 773 + deviceName); 774 } 775 // The pointer to the native input device is valid from here, so ensure that all 776 // failures close the device after this point. 777 try { 778 inputDeviceId = waiter.waitForDeviceCreation(); 779 780 binderDeathRecipient = new BinderDeathRecipient(deviceToken); 781 try { 782 deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0); 783 } catch (RemoteException e) { 784 throw new DeviceCreationException( 785 "Client died before virtual device could be created.", e); 786 } 787 } catch (DeviceCreationException e) { 788 mNativeWrapper.closeUinput(ptr); 789 throw e; 790 } 791 } catch (DeviceCreationException e) { 792 InputManagerGlobal.getInstance().removeUniqueIdAssociationByPort(phys); 793 throw e; 794 } 795 796 synchronized (mLock) { 797 mInputDeviceDescriptors.put(deviceToken, 798 new InputDeviceDescriptor(ptr, binderDeathRecipient, type, displayId, phys, 799 deviceName, inputDeviceId)); 800 } 801 802 if (android.companion.virtualdevice.flags.Flags.metricsCollection()) { 803 String metricId = getMetricIdForInputType(type); 804 if (metricId != null) { 805 Counter.logIncrementWithUid(metricId, mAttributionSource.getUid()); 806 } 807 } 808 } 809 getMetricIdForInputType(@nputDeviceDescriptor.Type int type)810 private static String getMetricIdForInputType(@InputDeviceDescriptor.Type int type) { 811 switch (type) { 812 case InputDeviceDescriptor.TYPE_KEYBOARD: 813 return "virtual_devices.value_virtual_keyboard_created_count"; 814 case InputDeviceDescriptor.TYPE_MOUSE: 815 return "virtual_devices.value_virtual_mouse_created_count"; 816 case InputDeviceDescriptor.TYPE_TOUCHSCREEN: 817 return "virtual_devices.value_virtual_touchscreen_created_count"; 818 case InputDeviceDescriptor.TYPE_DPAD: 819 return "virtual_devices.value_virtual_dpad_created_count"; 820 case InputDeviceDescriptor.TYPE_NAVIGATION_TOUCHPAD: 821 return "virtual_devices.value_virtual_navigationtouchpad_created_count"; 822 case InputDeviceDescriptor.TYPE_STYLUS: 823 return "virtual_devices.value_virtual_stylus_created_count"; 824 default: 825 Log.e(TAG, "No metric known for input type: " + type); 826 return null; 827 } 828 } 829 830 @VisibleForTesting 831 interface DeviceCreationThreadVerifier { 832 /** Returns true if the calling thread is a valid thread for device creation. */ isValidThread()833 boolean isValidThread(); 834 } 835 } 836