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