• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.Point;
23 import android.graphics.PointF;
24 import android.hardware.display.DisplayManagerInternal;
25 import android.hardware.input.InputDeviceIdentifier;
26 import android.hardware.input.InputManager;
27 import android.hardware.input.InputManagerInternal;
28 import android.hardware.input.VirtualKeyEvent;
29 import android.hardware.input.VirtualMouseButtonEvent;
30 import android.hardware.input.VirtualMouseRelativeEvent;
31 import android.hardware.input.VirtualMouseScrollEvent;
32 import android.hardware.input.VirtualTouchEvent;
33 import android.os.Handler;
34 import android.os.IBinder;
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 
46 import java.io.PrintWriter;
47 import java.lang.annotation.Retention;
48 import java.lang.annotation.RetentionPolicy;
49 import java.util.Iterator;
50 import java.util.Map;
51 import java.util.Objects;
52 import java.util.concurrent.CountDownLatch;
53 import java.util.concurrent.TimeUnit;
54 import java.util.concurrent.atomic.AtomicLong;
55 import java.util.function.Supplier;
56 
57 /** Controls virtual input devices, including device lifecycle and event dispatch. */
58 class InputController {
59 
60     private static final String TAG = "VirtualInputController";
61 
62     private static final AtomicLong sNextPhysId = new AtomicLong(1);
63 
64     static final String PHYS_TYPE_KEYBOARD = "Keyboard";
65     static final String PHYS_TYPE_MOUSE = "Mouse";
66     static final String PHYS_TYPE_TOUCHSCREEN = "Touchscreen";
67     @StringDef(prefix = { "PHYS_TYPE_" }, value = {
68             PHYS_TYPE_KEYBOARD,
69             PHYS_TYPE_MOUSE,
70             PHYS_TYPE_TOUCHSCREEN,
71     })
72     @Retention(RetentionPolicy.SOURCE)
73     @interface PhysType {
74     }
75 
76     private final Object mLock;
77 
78     /* Token -> file descriptor associations. */
79     @VisibleForTesting
80     @GuardedBy("mLock")
81     final Map<IBinder, InputDeviceDescriptor> mInputDeviceDescriptors = new ArrayMap<>();
82 
83     private final Handler mHandler;
84     private final NativeWrapper mNativeWrapper;
85     private final DisplayManagerInternal mDisplayManagerInternal;
86     private final InputManagerInternal mInputManagerInternal;
87     private final WindowManager mWindowManager;
88     private final DeviceCreationThreadVerifier mThreadVerifier;
89 
InputController(@onNull Object lock, @NonNull Handler handler, @NonNull WindowManager windowManager)90     InputController(@NonNull Object lock, @NonNull Handler handler,
91             @NonNull WindowManager windowManager) {
92         this(lock, new NativeWrapper(), handler, windowManager,
93                 // Verify that virtual devices are not created on the handler thread.
94                 () -> !handler.getLooper().isCurrentThread());
95     }
96 
97     @VisibleForTesting
InputController(@onNull Object lock, @NonNull NativeWrapper nativeWrapper, @NonNull Handler handler, @NonNull WindowManager windowManager, @NonNull DeviceCreationThreadVerifier threadVerifier)98     InputController(@NonNull Object lock, @NonNull NativeWrapper nativeWrapper,
99             @NonNull Handler handler, @NonNull WindowManager windowManager,
100             @NonNull DeviceCreationThreadVerifier threadVerifier) {
101         mLock = lock;
102         mHandler = handler;
103         mNativeWrapper = nativeWrapper;
104         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
105         mInputManagerInternal = LocalServices.getService(InputManagerInternal.class);
106         mWindowManager = windowManager;
107         mThreadVerifier = threadVerifier;
108     }
109 
close()110     void close() {
111         synchronized (mLock) {
112             final Iterator<Map.Entry<IBinder, InputDeviceDescriptor>> iterator =
113                     mInputDeviceDescriptors.entrySet().iterator();
114             if (iterator.hasNext()) {
115                 final Map.Entry<IBinder, InputDeviceDescriptor> entry = iterator.next();
116                 final IBinder token = entry.getKey();
117                 final InputDeviceDescriptor inputDeviceDescriptor = entry.getValue();
118                 iterator.remove();
119                 closeInputDeviceDescriptorLocked(token, inputDeviceDescriptor);
120             }
121         }
122     }
123 
createKeyboard(@onNull String deviceName, int vendorId, int productId, @NonNull IBinder deviceToken, int displayId)124     void createKeyboard(@NonNull String deviceName,
125             int vendorId,
126             int productId,
127             @NonNull IBinder deviceToken,
128             int displayId) {
129         final String phys = createPhys(PHYS_TYPE_KEYBOARD);
130         try {
131             createDeviceInternal(InputDeviceDescriptor.TYPE_KEYBOARD, deviceName, vendorId,
132                     productId, deviceToken, displayId, phys,
133                     () -> mNativeWrapper.openUinputKeyboard(deviceName, vendorId, productId, phys));
134         } catch (DeviceCreationException e) {
135             throw new RuntimeException(
136                     "Failed to create virtual keyboard device '" + deviceName + "'.", e);
137         }
138     }
139 
createMouse(@onNull String deviceName, int vendorId, int productId, @NonNull IBinder deviceToken, int displayId)140     void createMouse(@NonNull String deviceName,
141             int vendorId,
142             int productId,
143             @NonNull IBinder deviceToken,
144             int displayId) {
145         final String phys = createPhys(PHYS_TYPE_MOUSE);
146         try {
147             createDeviceInternal(InputDeviceDescriptor.TYPE_MOUSE, deviceName, vendorId, productId,
148                     deviceToken, displayId, phys,
149                     () -> mNativeWrapper.openUinputMouse(deviceName, vendorId, productId, phys));
150         } catch (DeviceCreationException e) {
151             throw new RuntimeException(
152                     "Failed to create virtual mouse device: '" + deviceName + "'.", e);
153         }
154         mInputManagerInternal.setVirtualMousePointerDisplayId(displayId);
155     }
156 
createTouchscreen(@onNull String deviceName, int vendorId, int productId, @NonNull IBinder deviceToken, int displayId, @NonNull Point screenSize)157     void createTouchscreen(@NonNull String deviceName,
158             int vendorId,
159             int productId,
160             @NonNull IBinder deviceToken,
161             int displayId,
162             @NonNull Point screenSize) {
163         final String phys = createPhys(PHYS_TYPE_TOUCHSCREEN);
164         try {
165             createDeviceInternal(InputDeviceDescriptor.TYPE_TOUCHSCREEN, deviceName, vendorId,
166                     productId, deviceToken, displayId, phys,
167                     () -> mNativeWrapper.openUinputTouchscreen(deviceName, vendorId, productId,
168                             phys, screenSize.y, screenSize.x));
169         } catch (DeviceCreationException e) {
170             throw new RuntimeException(
171                     "Failed to create virtual touchscreen device '" + deviceName + "'.", e);
172         }
173     }
174 
unregisterInputDevice(@onNull IBinder token)175     void unregisterInputDevice(@NonNull IBinder token) {
176         synchronized (mLock) {
177             final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.remove(
178                     token);
179             if (inputDeviceDescriptor == null) {
180                 throw new IllegalArgumentException(
181                         "Could not unregister input device for given token");
182             }
183             closeInputDeviceDescriptorLocked(token, inputDeviceDescriptor);
184         }
185     }
186 
187     @GuardedBy("mLock")
closeInputDeviceDescriptorLocked(IBinder token, InputDeviceDescriptor inputDeviceDescriptor)188     private void closeInputDeviceDescriptorLocked(IBinder token,
189             InputDeviceDescriptor inputDeviceDescriptor) {
190         token.unlinkToDeath(inputDeviceDescriptor.getDeathRecipient(), /* flags= */ 0);
191         mNativeWrapper.closeUinput(inputDeviceDescriptor.getFileDescriptor());
192         InputManager.getInstance().removeUniqueIdAssociation(inputDeviceDescriptor.getPhys());
193 
194         // Reset values to the default if all virtual mice are unregistered, or set display
195         // id if there's another mouse (choose the most recent). The inputDeviceDescriptor must be
196         // removed from the mInputDeviceDescriptors instance variable prior to this point.
197         if (inputDeviceDescriptor.isMouse()) {
198             if (mInputManagerInternal.getVirtualMousePointerDisplayId()
199                     == inputDeviceDescriptor.getDisplayId()) {
200                 updateActivePointerDisplayIdLocked();
201             }
202         }
203     }
204 
setShowPointerIcon(boolean visible, int displayId)205     void setShowPointerIcon(boolean visible, int displayId) {
206         mInputManagerInternal.setPointerIconVisible(visible, displayId);
207     }
208 
setPointerAcceleration(float pointerAcceleration, int displayId)209     void setPointerAcceleration(float pointerAcceleration, int displayId) {
210         mInputManagerInternal.setPointerAcceleration(pointerAcceleration, displayId);
211     }
212 
setDisplayEligibilityForPointerCapture(boolean isEligible, int displayId)213     void setDisplayEligibilityForPointerCapture(boolean isEligible, int displayId) {
214         mInputManagerInternal.setDisplayEligibilityForPointerCapture(displayId, isEligible);
215     }
216 
setLocalIme(int displayId)217     void setLocalIme(int displayId) {
218         // WM throws a SecurityException if the display is untrusted.
219         if ((mDisplayManagerInternal.getDisplayInfo(displayId).flags & Display.FLAG_TRUSTED)
220                 == Display.FLAG_TRUSTED) {
221             mWindowManager.setDisplayImePolicy(displayId,
222                     WindowManager.DISPLAY_IME_POLICY_LOCAL);
223         }
224     }
225 
226     @GuardedBy("mLock")
updateActivePointerDisplayIdLocked()227     private void updateActivePointerDisplayIdLocked() {
228         InputDeviceDescriptor mostRecentlyCreatedMouse = null;
229         for (InputDeviceDescriptor otherInputDeviceDescriptor : mInputDeviceDescriptors.values()) {
230             if (otherInputDeviceDescriptor.isMouse()) {
231                 if (mostRecentlyCreatedMouse == null
232                         || (otherInputDeviceDescriptor.getCreationOrderNumber()
233                         > mostRecentlyCreatedMouse.getCreationOrderNumber())) {
234                     mostRecentlyCreatedMouse = otherInputDeviceDescriptor;
235                 }
236             }
237         }
238         if (mostRecentlyCreatedMouse != null) {
239             mInputManagerInternal.setVirtualMousePointerDisplayId(
240                     mostRecentlyCreatedMouse.getDisplayId());
241         } else {
242             // All mice have been unregistered
243             mInputManagerInternal.setVirtualMousePointerDisplayId(Display.INVALID_DISPLAY);
244         }
245     }
246 
createPhys(@hysType String type)247     private static String createPhys(@PhysType String type) {
248         return String.format("virtual%s:%d", type, sNextPhysId.getAndIncrement());
249     }
250 
setUniqueIdAssociation(int displayId, String phys)251     private void setUniqueIdAssociation(int displayId, String phys) {
252         final String displayUniqueId = mDisplayManagerInternal.getDisplayInfo(displayId).uniqueId;
253         InputManager.getInstance().addUniqueIdAssociation(phys, displayUniqueId);
254     }
255 
sendKeyEvent(@onNull IBinder token, @NonNull VirtualKeyEvent event)256     boolean sendKeyEvent(@NonNull IBinder token, @NonNull VirtualKeyEvent event) {
257         synchronized (mLock) {
258             final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
259                     token);
260             if (inputDeviceDescriptor == null) {
261                 throw new IllegalArgumentException(
262                         "Could not send key event to input device for given token");
263             }
264             return mNativeWrapper.writeKeyEvent(inputDeviceDescriptor.getFileDescriptor(),
265                     event.getKeyCode(), event.getAction());
266         }
267     }
268 
sendButtonEvent(@onNull IBinder token, @NonNull VirtualMouseButtonEvent event)269     boolean sendButtonEvent(@NonNull IBinder token, @NonNull VirtualMouseButtonEvent event) {
270         synchronized (mLock) {
271             final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
272                     token);
273             if (inputDeviceDescriptor == null) {
274                 throw new IllegalArgumentException(
275                         "Could not send button event to input device for given token");
276             }
277             if (inputDeviceDescriptor.getDisplayId()
278                     != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
279                 throw new IllegalStateException(
280                         "Display id associated with this mouse is not currently targetable");
281             }
282             return mNativeWrapper.writeButtonEvent(inputDeviceDescriptor.getFileDescriptor(),
283                     event.getButtonCode(), event.getAction());
284         }
285     }
286 
sendTouchEvent(@onNull IBinder token, @NonNull VirtualTouchEvent event)287     boolean sendTouchEvent(@NonNull IBinder token, @NonNull VirtualTouchEvent event) {
288         synchronized (mLock) {
289             final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
290                     token);
291             if (inputDeviceDescriptor == null) {
292                 throw new IllegalArgumentException(
293                         "Could not send touch event to input device for given token");
294             }
295             return mNativeWrapper.writeTouchEvent(inputDeviceDescriptor.getFileDescriptor(),
296                     event.getPointerId(), event.getToolType(), event.getAction(), event.getX(),
297                     event.getY(), event.getPressure(), event.getMajorAxisSize());
298         }
299     }
300 
sendRelativeEvent(@onNull IBinder token, @NonNull VirtualMouseRelativeEvent event)301     boolean sendRelativeEvent(@NonNull IBinder token, @NonNull VirtualMouseRelativeEvent event) {
302         synchronized (mLock) {
303             final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
304                     token);
305             if (inputDeviceDescriptor == null) {
306                 throw new IllegalArgumentException(
307                         "Could not send relative event to input device for given token");
308             }
309             if (inputDeviceDescriptor.getDisplayId()
310                     != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
311                 throw new IllegalStateException(
312                         "Display id associated with this mouse is not currently targetable");
313             }
314             return mNativeWrapper.writeRelativeEvent(inputDeviceDescriptor.getFileDescriptor(),
315                     event.getRelativeX(), event.getRelativeY());
316         }
317     }
318 
sendScrollEvent(@onNull IBinder token, @NonNull VirtualMouseScrollEvent event)319     boolean sendScrollEvent(@NonNull IBinder token, @NonNull VirtualMouseScrollEvent event) {
320         synchronized (mLock) {
321             final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
322                     token);
323             if (inputDeviceDescriptor == null) {
324                 throw new IllegalArgumentException(
325                         "Could not send scroll event to input device for given token");
326             }
327             if (inputDeviceDescriptor.getDisplayId()
328                     != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
329                 throw new IllegalStateException(
330                         "Display id associated with this mouse is not currently targetable");
331             }
332             return mNativeWrapper.writeScrollEvent(inputDeviceDescriptor.getFileDescriptor(),
333                     event.getXAxisMovement(), event.getYAxisMovement());
334         }
335     }
336 
getCursorPosition(@onNull IBinder token)337     public PointF getCursorPosition(@NonNull IBinder token) {
338         synchronized (mLock) {
339             final InputDeviceDescriptor inputDeviceDescriptor = mInputDeviceDescriptors.get(
340                     token);
341             if (inputDeviceDescriptor == null) {
342                 throw new IllegalArgumentException(
343                         "Could not get cursor position for input device for given token");
344             }
345             if (inputDeviceDescriptor.getDisplayId()
346                     != mInputManagerInternal.getVirtualMousePointerDisplayId()) {
347                 throw new IllegalStateException(
348                         "Display id associated with this mouse is not currently targetable");
349             }
350             return LocalServices.getService(InputManagerInternal.class).getCursorPosition();
351         }
352     }
353 
dump(@onNull PrintWriter fout)354     public void dump(@NonNull PrintWriter fout) {
355         fout.println("    InputController: ");
356         synchronized (mLock) {
357             fout.println("      Active descriptors: ");
358             for (InputDeviceDescriptor inputDeviceDescriptor : mInputDeviceDescriptors.values()) {
359                 fout.println("        fd: " + inputDeviceDescriptor.getFileDescriptor());
360                 fout.println("          displayId: " + inputDeviceDescriptor.getDisplayId());
361                 fout.println("          creationOrder: "
362                         + inputDeviceDescriptor.getCreationOrderNumber());
363                 fout.println("          type: " + inputDeviceDescriptor.getType());
364                 fout.println("          phys: " + inputDeviceDescriptor.getPhys());
365             }
366         }
367     }
368 
nativeOpenUinputKeyboard(String deviceName, int vendorId, int productId, String phys)369     private static native int nativeOpenUinputKeyboard(String deviceName, int vendorId,
370             int productId, String phys);
nativeOpenUinputMouse(String deviceName, int vendorId, int productId, String phys)371     private static native int nativeOpenUinputMouse(String deviceName, int vendorId, int productId,
372             String phys);
nativeOpenUinputTouchscreen(String deviceName, int vendorId, int productId, String phys, int height, int width)373     private static native int nativeOpenUinputTouchscreen(String deviceName, int vendorId,
374             int productId, String phys, int height, int width);
nativeCloseUinput(int fd)375     private static native boolean nativeCloseUinput(int fd);
nativeWriteKeyEvent(int fd, int androidKeyCode, int action)376     private static native boolean nativeWriteKeyEvent(int fd, int androidKeyCode, int action);
nativeWriteButtonEvent(int fd, int buttonCode, int action)377     private static native boolean nativeWriteButtonEvent(int fd, int buttonCode, int action);
nativeWriteTouchEvent(int fd, int pointerId, int toolType, int action, float locationX, float locationY, float pressure, float majorAxisSize)378     private static native boolean nativeWriteTouchEvent(int fd, int pointerId, int toolType,
379             int action, float locationX, float locationY, float pressure, float majorAxisSize);
nativeWriteRelativeEvent(int fd, float relativeX, float relativeY)380     private static native boolean nativeWriteRelativeEvent(int fd, float relativeX,
381             float relativeY);
nativeWriteScrollEvent(int fd, float xAxisMovement, float yAxisMovement)382     private static native boolean nativeWriteScrollEvent(int fd, float xAxisMovement,
383             float yAxisMovement);
384 
385     /** Wrapper around the static native methods for tests. */
386     @VisibleForTesting
387     protected static class NativeWrapper {
openUinputKeyboard(String deviceName, int vendorId, int productId, String phys)388         public int openUinputKeyboard(String deviceName, int vendorId, int productId, String phys) {
389             return nativeOpenUinputKeyboard(deviceName, vendorId, productId, phys);
390         }
391 
openUinputMouse(String deviceName, int vendorId, int productId, String phys)392         public int openUinputMouse(String deviceName, int vendorId, int productId, String phys) {
393             return nativeOpenUinputMouse(deviceName, vendorId, productId, phys);
394         }
395 
openUinputTouchscreen(String deviceName, int vendorId, int productId, String phys, int height, int width)396         public int openUinputTouchscreen(String deviceName, int vendorId,
397                 int productId, String phys, int height, int width) {
398             return nativeOpenUinputTouchscreen(deviceName, vendorId, productId, phys, height,
399                     width);
400         }
401 
closeUinput(int fd)402         public boolean closeUinput(int fd) {
403             return nativeCloseUinput(fd);
404         }
405 
writeKeyEvent(int fd, int androidKeyCode, int action)406         public boolean writeKeyEvent(int fd, int androidKeyCode, int action) {
407             return nativeWriteKeyEvent(fd, androidKeyCode, action);
408         }
409 
writeButtonEvent(int fd, int buttonCode, int action)410         public boolean writeButtonEvent(int fd, int buttonCode, int action) {
411             return nativeWriteButtonEvent(fd, buttonCode, action);
412         }
413 
writeTouchEvent(int fd, int pointerId, int toolType, int action, float locationX, float locationY, float pressure, float majorAxisSize)414         public boolean writeTouchEvent(int fd, int pointerId, int toolType, int action,
415                 float locationX, float locationY, float pressure, float majorAxisSize) {
416             return nativeWriteTouchEvent(fd, pointerId, toolType,
417                     action, locationX, locationY,
418                     pressure, majorAxisSize);
419         }
420 
writeRelativeEvent(int fd, float relativeX, float relativeY)421         public boolean writeRelativeEvent(int fd, float relativeX, float relativeY) {
422             return nativeWriteRelativeEvent(fd, relativeX, relativeY);
423         }
424 
writeScrollEvent(int fd, float xAxisMovement, float yAxisMovement)425         public boolean writeScrollEvent(int fd, float xAxisMovement, float yAxisMovement) {
426             return nativeWriteScrollEvent(fd, xAxisMovement,
427                     yAxisMovement);
428         }
429     }
430 
431     @VisibleForTesting static final class InputDeviceDescriptor {
432 
433         static final int TYPE_KEYBOARD = 1;
434         static final int TYPE_MOUSE = 2;
435         static final int TYPE_TOUCHSCREEN = 3;
436         @IntDef(prefix = { "TYPE_" }, value = {
437                 TYPE_KEYBOARD,
438                 TYPE_MOUSE,
439                 TYPE_TOUCHSCREEN,
440         })
441         @Retention(RetentionPolicy.SOURCE)
442         @interface Type {
443         }
444 
445         private static final AtomicLong sNextCreationOrderNumber = new AtomicLong(1);
446 
447         private final int mFd;
448         private final IBinder.DeathRecipient mDeathRecipient;
449         private final @Type int mType;
450         private final int mDisplayId;
451         private final String mPhys;
452         // Monotonically increasing number; devices with lower numbers were created earlier.
453         private final long mCreationOrderNumber;
454 
InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient, @Type int type, int displayId, String phys)455         InputDeviceDescriptor(int fd, IBinder.DeathRecipient deathRecipient, @Type int type,
456                 int displayId, String phys) {
457             mFd = fd;
458             mDeathRecipient = deathRecipient;
459             mType = type;
460             mDisplayId = displayId;
461             mPhys = phys;
462             mCreationOrderNumber = sNextCreationOrderNumber.getAndIncrement();
463         }
464 
getFileDescriptor()465         public int getFileDescriptor() {
466             return mFd;
467         }
468 
getType()469         public int getType() {
470             return mType;
471         }
472 
isMouse()473         public boolean isMouse() {
474             return mType == TYPE_MOUSE;
475         }
476 
getDeathRecipient()477         public IBinder.DeathRecipient getDeathRecipient() {
478             return mDeathRecipient;
479         }
480 
getDisplayId()481         public int getDisplayId() {
482             return mDisplayId;
483         }
484 
getCreationOrderNumber()485         public long getCreationOrderNumber() {
486             return mCreationOrderNumber;
487         }
488 
getPhys()489         public String getPhys() {
490             return mPhys;
491         }
492     }
493 
494     private final class BinderDeathRecipient implements IBinder.DeathRecipient {
495 
496         private final IBinder mDeviceToken;
497 
BinderDeathRecipient(IBinder deviceToken)498         BinderDeathRecipient(IBinder deviceToken) {
499             mDeviceToken = deviceToken;
500         }
501 
502         @Override
binderDied()503         public void binderDied() {
504             // All callers are expected to call {@link VirtualDevice#unregisterInputDevice} before
505             // quitting, which removes this death recipient. If this is invoked, the remote end
506             // died, or they disposed of the object without properly unregistering.
507             Slog.e(TAG, "Virtual input controller binder died");
508             unregisterInputDevice(mDeviceToken);
509         }
510     }
511 
512     /** A helper class used to wait for an input device to be registered. */
513     private class WaitForDevice implements AutoCloseable {
514         private final CountDownLatch mDeviceAddedLatch = new CountDownLatch(1);
515         private final InputManager.InputDeviceListener mListener;
516 
WaitForDevice(String deviceName, int vendorId, int productId)517         WaitForDevice(String deviceName, int vendorId, int productId) {
518             mListener = new InputManager.InputDeviceListener() {
519                 @Override
520                 public void onInputDeviceAdded(int deviceId) {
521                     final InputDevice device = InputManager.getInstance().getInputDevice(
522                             deviceId);
523                     Objects.requireNonNull(device, "Newly added input device was null.");
524                     if (!device.getName().equals(deviceName)) {
525                         return;
526                     }
527                     final InputDeviceIdentifier id = device.getIdentifier();
528                     if (id.getVendorId() != vendorId || id.getProductId() != productId) {
529                         return;
530                     }
531                     mDeviceAddedLatch.countDown();
532                 }
533 
534                 @Override
535                 public void onInputDeviceRemoved(int deviceId) {
536 
537                 }
538 
539                 @Override
540                 public void onInputDeviceChanged(int deviceId) {
541 
542                 }
543             };
544             InputManager.getInstance().registerInputDeviceListener(mListener, mHandler);
545         }
546 
547         /** Note: This must not be called from {@link #mHandler}'s thread. */
waitForDeviceCreation()548         void waitForDeviceCreation() throws DeviceCreationException {
549             try {
550                 if (!mDeviceAddedLatch.await(1, TimeUnit.MINUTES)) {
551                     throw new DeviceCreationException(
552                             "Timed out waiting for virtual device to be created.");
553                 }
554             } catch (InterruptedException e) {
555                 throw new DeviceCreationException(
556                         "Interrupted while waiting for virtual device to be created.", e);
557             }
558         }
559 
560         @Override
close()561         public void close() {
562             InputManager.getInstance().unregisterInputDeviceListener(mListener);
563         }
564     }
565 
566     /** An internal exception that is thrown to indicate an error when opening a virtual device. */
567     private static class DeviceCreationException extends Exception {
DeviceCreationException(String message)568         DeviceCreationException(String message) {
569             super(message);
570         }
DeviceCreationException(String message, Exception cause)571         DeviceCreationException(String message, Exception cause) {
572             super(message, cause);
573         }
574     }
575 
576     /**
577      * Creates a virtual input device synchronously, and waits for the notification that the device
578      * was added.
579      *
580      * Note: Input device creation is expected to happen on a binder thread, and the calling thread
581      * will be blocked until the input device creation is successful. This should not be called on
582      * the handler's thread.
583      *
584      * @throws DeviceCreationException Throws this exception if anything unexpected happens in the
585      *                                 process of creating the device. This method will take care
586      *                                 to restore the state of the system in the event of any
587      *                                 unexpected behavior.
588      */
createDeviceInternal(@nputDeviceDescriptor.Type int type, String deviceName, int vendorId, int productId, IBinder deviceToken, int displayId, String phys, Supplier<Integer> deviceOpener)589     private void createDeviceInternal(@InputDeviceDescriptor.Type int type, String deviceName,
590             int vendorId, int productId, IBinder deviceToken, int displayId, String phys,
591             Supplier<Integer> deviceOpener)
592             throws DeviceCreationException {
593         if (!mThreadVerifier.isValidThread()) {
594             throw new IllegalStateException(
595                     "Virtual device creation should happen on an auxiliary thread (e.g. binder "
596                             + "thread) and not from the handler's thread.");
597         }
598 
599         final int fd;
600         final BinderDeathRecipient binderDeathRecipient;
601 
602         setUniqueIdAssociation(displayId, phys);
603         try (WaitForDevice waiter = new WaitForDevice(deviceName, vendorId, productId)) {
604             fd = deviceOpener.get();
605             if (fd < 0) {
606                 throw new DeviceCreationException(
607                         "A native error occurred when creating touchscreen: " + -fd);
608             }
609             // The fd is valid from here, so ensure that all failures close the fd after this point.
610             try {
611                 waiter.waitForDeviceCreation();
612 
613                 binderDeathRecipient = new BinderDeathRecipient(deviceToken);
614                 try {
615                     deviceToken.linkToDeath(binderDeathRecipient, /* flags= */ 0);
616                 } catch (RemoteException e) {
617                     throw new DeviceCreationException(
618                             "Client died before virtual device could be created.", e);
619                 }
620             } catch (DeviceCreationException e) {
621                 mNativeWrapper.closeUinput(fd);
622                 throw e;
623             }
624         } catch (DeviceCreationException e) {
625             InputManager.getInstance().removeUniqueIdAssociation(phys);
626             throw e;
627         }
628 
629         synchronized (mLock) {
630             mInputDeviceDescriptors.put(deviceToken,
631                     new InputDeviceDescriptor(fd, binderDeathRecipient, type, displayId, phys));
632         }
633     }
634 
635     @VisibleForTesting
636     interface DeviceCreationThreadVerifier {
637         /** Returns true if the calling thread is a valid thread for device creation. */
isValidThread()638         boolean isValidThread();
639     }
640 }
641