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