1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.companion.virtual;
18 
19 import static org.mockito.ArgumentMatchers.anyInt;
20 import static org.mockito.ArgumentMatchers.anyString;
21 import static org.mockito.ArgumentMatchers.notNull;
22 import static org.mockito.Mockito.doAnswer;
23 import static org.mockito.Mockito.when;
24 
25 import android.hardware.input.IInputDevicesChangedListener;
26 import android.hardware.input.IInputManager;
27 import android.hardware.input.InputManagerGlobal;
28 import android.os.RemoteException;
29 import android.testing.TestableLooper;
30 import android.view.Display;
31 import android.view.InputDevice;
32 
33 import org.mockito.invocation.InvocationOnMock;
34 
35 import java.util.ArrayList;
36 import java.util.HashMap;
37 import java.util.List;
38 import java.util.Map;
39 import java.util.Objects;
40 import java.util.stream.IntStream;
41 
42 /**
43  * A test utility class used to share the logic for setting up
44  * {@link  android.hardware.input.InputManager}'s callback for
45  * when a virtual input device being added.
46  */
47 class InputManagerMockHelper {
48     private final TestableLooper mTestableLooper;
49     private final InputController.NativeWrapper mNativeWrapperMock;
50     private final IInputManager mIInputManagerMock;
51     private final InputManagerGlobal.TestSession mInputManagerGlobalSession;
52     private final List<InputDevice> mDevices = new ArrayList<>();
53     private IInputDevicesChangedListener mDevicesChangedListener;
54     private final Map<String /* uniqueId */, Integer /* displayId */> mDisplayIdMapping =
55             new HashMap<>();
56     private final Map<String /* phys */, String /* uniqueId */> mUniqueIdAssociationByPort =
57             new HashMap<>();
58 
InputManagerMockHelper(TestableLooper testableLooper, InputController.NativeWrapper nativeWrapperMock, IInputManager iInputManagerMock)59     InputManagerMockHelper(TestableLooper testableLooper,
60             InputController.NativeWrapper nativeWrapperMock, IInputManager iInputManagerMock)
61             throws Exception {
62         mTestableLooper = testableLooper;
63         mNativeWrapperMock = nativeWrapperMock;
64         mIInputManagerMock = iInputManagerMock;
65 
66         doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputMouse(
67                 anyString(), anyInt(), anyInt(), anyString());
68         doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputDpad(
69                 anyString(), anyInt(), anyInt(), anyString());
70         doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputKeyboard(
71                 anyString(), anyInt(), anyInt(), anyString());
72         doAnswer(this::handleNativeOpenInputDevice).when(mNativeWrapperMock).openUinputTouchscreen(
73                 anyString(), anyInt(), anyInt(), anyString(), anyInt(), anyInt());
74 
75         doAnswer(inv -> {
76             mDevicesChangedListener = inv.getArgument(0);
77             return null;
78         }).when(mIInputManagerMock).registerInputDevicesChangedListener(notNull());
79         when(mIInputManagerMock.getInputDeviceIds()).thenReturn(new int[0]);
80         doAnswer(inv -> mDevices.get(inv.getArgument(0)))
81                 .when(mIInputManagerMock).getInputDevice(anyInt());
82         doAnswer(inv -> mUniqueIdAssociationByPort.put(inv.getArgument(0),
83                 inv.getArgument(1))).when(mIInputManagerMock).addUniqueIdAssociationByPort(
84                         anyString(), anyString());
85         doAnswer(inv -> mUniqueIdAssociationByPort.remove(inv.getArgument(0))).when(
86                 mIInputManagerMock).removeUniqueIdAssociationByPort(anyString());
87 
88         // Set a new instance of InputManager for testing that uses the IInputManager mock as the
89         // interface to the server.
90         mInputManagerGlobalSession = InputManagerGlobal.createTestSession(mIInputManagerMock);
91     }
92 
tearDown()93     public void tearDown() {
94         if (mInputManagerGlobalSession != null) {
95             mInputManagerGlobalSession.close();
96         }
97     }
98 
addDisplayIdMapping(String uniqueId, int displayId)99     public void addDisplayIdMapping(String uniqueId, int displayId) {
100         mDisplayIdMapping.put(uniqueId, displayId);
101     }
102 
handleNativeOpenInputDevice(InvocationOnMock inv)103     private long handleNativeOpenInputDevice(InvocationOnMock inv) {
104         Objects.requireNonNull(mDevicesChangedListener,
105                 "InputController did not register an InputDevicesChangedListener.");
106 
107         final String phys = inv.getArgument(3);
108         final InputDevice device = new InputDevice.Builder()
109                 .setId(mDevices.size())
110                 .setName(inv.getArgument(0))
111                 .setVendorId(inv.getArgument(1))
112                 .setProductId(inv.getArgument(2))
113                 .setDescriptor(phys)
114                 .setExternal(true)
115                 .setAssociatedDisplayId(
116                         mDisplayIdMapping.getOrDefault(mUniqueIdAssociationByPort.get(phys),
117                                 Display.INVALID_DISPLAY))
118                 .build();
119 
120         mDevices.add(device);
121         try {
122             mDevicesChangedListener.onInputDevicesChanged(
123                     mDevices.stream().flatMapToInt(
124                             d -> IntStream.of(d.getId(), d.getGeneration())).toArray());
125         } catch (RemoteException ignored) {
126         }
127         // Process the device added notification.
128         mTestableLooper.processAllMessages();
129         // Return a placeholder pointer to the native input device.
130         return 1L;
131     }
132 }
133