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