• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.google.common.truth.Truth.assertWithMessage;
20 
21 import static org.junit.Assert.assertThrows;
22 import static org.mockito.ArgumentMatchers.anyString;
23 import static org.mockito.ArgumentMatchers.eq;
24 import static org.mockito.ArgumentMatchers.startsWith;
25 import static org.mockito.Mockito.doAnswer;
26 import static org.mockito.Mockito.verify;
27 
28 import android.content.AttributionSource;
29 import android.hardware.display.DisplayManagerInternal;
30 import android.hardware.input.IInputManager;
31 import android.hardware.input.InputManagerGlobal;
32 import android.os.Binder;
33 import android.os.Handler;
34 import android.os.IBinder;
35 import android.platform.test.annotations.Presubmit;
36 import android.testing.AndroidTestingRunner;
37 import android.testing.TestableLooper;
38 import android.view.DisplayInfo;
39 import android.view.WindowManager;
40 
41 import androidx.test.InstrumentationRegistry;
42 
43 import com.android.server.LocalServices;
44 import com.android.server.input.InputManagerInternal;
45 
46 import org.junit.After;
47 import org.junit.Before;
48 import org.junit.Test;
49 import org.junit.runner.RunWith;
50 import org.mockito.Mock;
51 import org.mockito.MockitoAnnotations;
52 
53 @Presubmit
54 @RunWith(AndroidTestingRunner.class)
55 @TestableLooper.RunWithLooper(setAsMainLooper = true)
56 public class InputControllerTest {
57     private static final String LANGUAGE_TAG = "en-US";
58     private static final String LAYOUT_TYPE = "qwerty";
59 
60     @Mock
61     private InputManagerInternal mInputManagerInternalMock;
62     @Mock
63     private DisplayManagerInternal mDisplayManagerInternalMock;
64     @Mock
65     private InputController.NativeWrapper mNativeWrapperMock;
66     @Mock
67     private IInputManager mIInputManagerMock;
68 
69     private InputManagerMockHelper mInputManagerMockHelper;
70     private InputController mInputController;
71 
72     @Before
setUp()73     public void setUp() throws Exception {
74         MockitoAnnotations.initMocks(this);
75         mInputManagerMockHelper = new InputManagerMockHelper(
76                 TestableLooper.get(this), mNativeWrapperMock, mIInputManagerMock);
77 
78         LocalServices.removeServiceForTest(InputManagerInternal.class);
79         LocalServices.addService(InputManagerInternal.class, mInputManagerInternalMock);
80 
81         setUpDisplay(1 /* displayId */);
82         setUpDisplay(2 /* displayId */);
83         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
84         LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
85 
86         // Allow virtual devices to be created on the looper thread for testing.
87         final InputController.DeviceCreationThreadVerifier threadVerifier = () -> true;
88         mInputController = new InputController(mNativeWrapperMock,
89                 new Handler(TestableLooper.get(this).getLooper()),
90                 InstrumentationRegistry.getTargetContext().getSystemService(WindowManager.class),
91                 AttributionSource.myAttributionSource(),
92                 threadVerifier);
93     }
94 
setUpDisplay(int displayId)95     void setUpDisplay(int displayId) {
96         final String uniqueId = "uniqueId:" + displayId;
97         doAnswer((inv) -> {
98             final DisplayInfo displayInfo = new DisplayInfo();
99             displayInfo.uniqueId = uniqueId;
100             return displayInfo;
101         }).when(mDisplayManagerInternalMock).getDisplayInfo(eq(displayId));
102         mInputManagerMockHelper.addDisplayIdMapping(uniqueId, displayId);
103     }
104 
105     @After
tearDown()106     public void tearDown() {
107         mInputManagerMockHelper.tearDown();
108     }
109 
110     @Test
registerInputDevice_deviceCreation_hasDeviceId()111     public void registerInputDevice_deviceCreation_hasDeviceId() throws Exception {
112         final IBinder device1Token = new Binder("device1");
113         mInputController.createMouse("mouse", /*vendorId= */ 1, /*productId= */ 1, device1Token,
114                 /* displayId= */ 1);
115         int device1Id = mInputController.getInputDeviceId(device1Token);
116 
117         final IBinder device2Token = new Binder("device2");
118         mInputController.createKeyboard("keyboard", /*vendorId= */2, /*productId= */ 2,
119                 device2Token, 2, LANGUAGE_TAG, LAYOUT_TYPE);
120         int device2Id = mInputController.getInputDeviceId(device2Token);
121 
122         assertWithMessage("Different devices should have different id").that(
123                 device1Id).isNotEqualTo(device2Id);
124 
125 
126         int[] deviceIds = InputManagerGlobal.getInstance().getInputDeviceIds();
127         assertWithMessage("InputManager's deviceIds list should contain id of device 1").that(
128                 deviceIds).asList().contains(device1Id);
129         assertWithMessage("InputManager's deviceIds list should contain id of device 2").that(
130                 deviceIds).asList().contains(device2Id);
131 
132     }
133 
134     @Test
unregisterInputDevice_allMiceUnregistered_clearPointerDisplayId()135     public void unregisterInputDevice_allMiceUnregistered_clearPointerDisplayId() throws Exception {
136         final IBinder deviceToken = new Binder();
137         mInputController.createMouse("name", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
138                 /* displayId= */ 1);
139         verify(mNativeWrapperMock).openUinputMouse(eq("name"), eq(1), eq(1), anyString());
140         mInputController.unregisterInputDevice(deviceToken);
141     }
142 
143     @Test
unregisterInputDevice_anotherMouseExists_setPointerDisplayIdOverride()144     public void unregisterInputDevice_anotherMouseExists_setPointerDisplayIdOverride()
145             throws Exception {
146         final IBinder deviceToken = new Binder();
147         mInputController.createMouse("mouse1", /*vendorId= */ 1, /*productId= */ 1, deviceToken,
148                 /* displayId= */ 1);
149         verify(mNativeWrapperMock).openUinputMouse(eq("mouse1"), eq(1), eq(1), anyString());
150         final IBinder deviceToken2 = new Binder();
151         mInputController.createMouse("mouse2", /*vendorId= */ 1, /*productId= */ 1, deviceToken2,
152                 /* displayId= */ 2);
153         verify(mNativeWrapperMock).openUinputMouse(eq("mouse2"), eq(1), eq(1), anyString());
154         mInputController.unregisterInputDevice(deviceToken);
155     }
156 
157     @Test
createNavigationTouchpad_hasDeviceId()158     public void createNavigationTouchpad_hasDeviceId() throws Exception {
159         final IBinder deviceToken = new Binder();
160         mInputController.createNavigationTouchpad("name", /*vendorId= */ 1, /*productId= */ 1,
161                 deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50);
162 
163         int deviceId = mInputController.getInputDeviceId(deviceToken);
164         int[] deviceIds = InputManagerGlobal.getInstance().getInputDeviceIds();
165 
166         assertWithMessage("InputManager's deviceIds list should contain id of the device").that(
167             deviceIds).asList().contains(deviceId);
168     }
169 
170     @Test
createNavigationTouchpad_setsTypeAssociation()171     public void createNavigationTouchpad_setsTypeAssociation() throws Exception {
172         final IBinder deviceToken = new Binder();
173         mInputController.createNavigationTouchpad("name", /*vendorId= */ 1, /*productId= */ 1,
174                 deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50);
175 
176         verify(mInputManagerInternalMock).setTypeAssociation(
177                 startsWith("virtualNavigationTouchpad:"), eq("touchNavigation"));
178     }
179 
180     @Test
createAndUnregisterNavigationTouchpad_unsetsTypeAssociation()181     public void createAndUnregisterNavigationTouchpad_unsetsTypeAssociation() throws Exception {
182         final IBinder deviceToken = new Binder();
183         mInputController.createNavigationTouchpad("name", /*vendorId= */ 1, /*productId= */ 1,
184                 deviceToken, /* displayId= */ 1, /* touchpadHeight= */ 50, /* touchpadWidth= */ 50);
185 
186         mInputController.unregisterInputDevice(deviceToken);
187 
188         verify(mInputManagerInternalMock).unsetTypeAssociation(
189                 startsWith("virtualNavigationTouchpad:"));
190     }
191 
192     @Test
createKeyboard_addAndRemoveKeyboardLayoutAssociation()193     public void createKeyboard_addAndRemoveKeyboardLayoutAssociation() throws Exception {
194         final IBinder deviceToken = new Binder("device");
195 
196         mInputController.createKeyboard("keyboard", /*vendorId= */2, /*productId= */ 2, deviceToken,
197                 2, LANGUAGE_TAG, LAYOUT_TYPE);
198         verify(mInputManagerInternalMock).addKeyboardLayoutAssociation(anyString(),
199                 eq(LANGUAGE_TAG), eq(LAYOUT_TYPE));
200 
201         mInputController.unregisterInputDevice(deviceToken);
202         verify(mInputManagerInternalMock).removeKeyboardLayoutAssociation(anyString());
203     }
204 
205     @Test
createInputDevice_duplicateNamesAreNotAllowed()206     public void createInputDevice_duplicateNamesAreNotAllowed() throws Exception {
207         final IBinder deviceToken1 = new Binder("deviceToken1");
208         final IBinder deviceToken2 = new Binder("deviceToken2");
209 
210         final String sharedDeviceName = "DeviceName";
211 
212         mInputController.createDpad(sharedDeviceName, /*vendorId= */4, /*productId=*/4,
213                 deviceToken1, 1);
214         assertThrows("Device names need to be unique",
215                 InputController.DeviceCreationException.class,
216                 () -> mInputController.createDpad(
217                         sharedDeviceName, /*vendorId= */5, /*productId=*/5, deviceToken2, 2));
218     }
219 }
220