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