blob: eb53bae6cc115eeea20f5288acbb59696f9e3554 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.view.cts.input;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import android.app.Instrumentation;
import android.hardware.input.InputManager;
import android.view.InputDevice;
import android.view.KeyEvent;
import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ActivityTestRule;
import com.android.compatibility.common.util.WindowUtil;
import com.android.cts.input.UinputDevice;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.Arrays;
/**
* CTS test cases for multi device key events verification.
* This test utilize uinput command line tool to create multiple test devices, and configure the
* virtual device to have keys need to be tested. The JSON format input for device configuration
* and EV_KEY injection will be created directly from this test for uinput command.
* The test cases will inject evdev events from different virtual input devices and verify the
* received key events to verify the device Id, repeat count to be expected, as well as the key
* repeat behavior is consistently meeting expectations with multi devices.
*/
@MediumTest
@RunWith(AndroidJUnit4.class)
public class InputDeviceMultiDeviceKeyEventTest {
private static final String TAG = "InputDeviceMultiDeviceKeyEventTest";
private static final String LABEL_PREFIX = "KEYCODE_";
private static final int DEVICE_ID = 1;
private static final int EV_SYN = 0;
private static final int EV_KEY = 1;
private static final int EV_KEY_DOWN = 1;
private static final int EV_KEY_UP = 0;
private static final int UI_SET_EVBIT = 100;
private static final int UI_SET_KEYBIT = 101;
private static final int EV_KEY_CODE_1 = 2;
private static final int EV_KEY_CODE_2 = 3;
private static final int GOOGLE_VENDOR_ID = 0x18d1;
private static final int GOOGLE_VIRTUAL_KEYBOARD_ID = 0x001f;
private static final int NUM_DEVICES = 2;
private static final int POLL_EVENT_TIMEOUT_SECONDS = 1;
private static final int RETRY_COUNT = 10;
private Instrumentation mInstrumentation;
private InputManager mInputManager;
private UinputDevice[] mUinputDevices = new UinputDevice[NUM_DEVICES];
private int[] mInputManagerDeviceIds = new int[NUM_DEVICES];
private final int[] mEvKeys = {
EV_KEY_CODE_1,
EV_KEY_CODE_2
};
@Rule
public ActivityTestRule<InputDeviceKeyLayoutMapTestActivity> mActivityRule =
new ActivityTestRule<>(InputDeviceKeyLayoutMapTestActivity.class);
@Before
public void setup() {
mInstrumentation = InstrumentationRegistry.getInstrumentation();
WindowUtil.waitForFocus(mActivityRule.getActivity());
for (int i = 0; i < NUM_DEVICES; i++) {
final int jsonDeviceId = i + 1;
mUinputDevices[i] = new UinputDevice(mInstrumentation, jsonDeviceId,
GOOGLE_VENDOR_ID, GOOGLE_VIRTUAL_KEYBOARD_ID + jsonDeviceId,
InputDevice.SOURCE_KEYBOARD,
createDeviceRegisterCommand(jsonDeviceId, mEvKeys));
}
mInputManager = mInstrumentation.getContext().getSystemService(InputManager.class);
final int[] inputDeviceIds = mInputManager.getInputDeviceIds();
for (int inputDeviceId : inputDeviceIds) {
final InputDevice inputDevice = mInputManager.getInputDevice(inputDeviceId);
final int index = inputDevice.getProductId() - GOOGLE_VIRTUAL_KEYBOARD_ID - 1;
if (inputDevice.getVendorId() == GOOGLE_VENDOR_ID
&& index >= 0 && index < NUM_DEVICES) {
mInputManagerDeviceIds[index] = inputDeviceId;
}
}
}
@After
public void tearDown() {
for (int i = 0; i < NUM_DEVICES; i++) {
if (mUinputDevices[i] != null) {
mUinputDevices[i].close();
}
}
}
/**
* Create the uinput device registration command, in JSON format of uinput commandline tool.
* Refer to {@link framework/base/cmds/uinput/README.md}
*/
private String createDeviceRegisterCommand(int deviceId, int[] keys) {
JSONObject json = new JSONObject();
JSONArray arrayConfigs = new JSONArray();
try {
json.put("id", deviceId);
json.put("type", "uinput");
json.put("command", "register");
json.put("name", "Virtual All Buttons Device (Test)");
json.put("vid", GOOGLE_VENDOR_ID);
json.put("pid", GOOGLE_VIRTUAL_KEYBOARD_ID + deviceId);
json.put("bus", "bluetooth");
JSONObject jsonSetEvBit = new JSONObject();
JSONArray arraySetEvBit = new JSONArray();
arraySetEvBit.put(EV_KEY);
jsonSetEvBit.put("type", UI_SET_EVBIT);
jsonSetEvBit.put("data", arraySetEvBit);
arrayConfigs.put(jsonSetEvBit);
// Configure device have all keys from key layout map.
JSONArray arraySetKeyBit = new JSONArray();
for (int i = 0; i < keys.length; i++) {
arraySetKeyBit.put(keys[i]);
}
JSONObject jsonSetKeyBit = new JSONObject();
jsonSetKeyBit.put("type", UI_SET_KEYBIT);
jsonSetKeyBit.put("data", arraySetKeyBit);
arrayConfigs.put(jsonSetKeyBit);
json.put("configuration", arrayConfigs);
} catch (JSONException e) {
throw new RuntimeException(
"Could not create JSON object");
}
return json.toString();
}
/**
* Get a KeyEvent from event queue or timeout.
* The test activity instance may change in the middle, calling getKeyEvent with the old
* activity instance will get timed out when test activity instance changed. Rather than
* doing a long wait for timeout with same activity instance, break the polling into a number
* of retries and each time of retry call the ActivityTestRule.getActivity for current activity
* instance to avoid the test failure because of polling the old activity instance get timed
* out consequently failed the test.
*
* @param retryCount The times to retry get KeyEvent from test activity.
*
* @return KeyEvent delivered to test activity, null if timeout.
*/
private KeyEvent getKeyEvent(int retryCount) {
for (int i = 0; i < retryCount; i++) {
KeyEvent event = mActivityRule.getActivity().getKeyEvent(POLL_EVENT_TIMEOUT_SECONDS);
if (event != null) {
return event;
}
}
return null;
}
private void assertNoKeyEvent() {
assertNull(getKeyEvent(1 /* retryCount */));
}
/**
* Asserts that the application received a {@link android.view.KeyEvent} with the given
* metadata.
*
* If other KeyEvents are received by the application prior to the expected KeyEvent, or no
* KeyEvents are received within a reasonable amount of time, then this will throw an
* {@link AssertionError}.
*
* Only action, source, keyCode and metaState are being compared.
*/
private void assertReceivedKeyEvent(@NonNull KeyEvent expectedKeyEvent) {
assertNotEquals(expectedKeyEvent.getKeyCode(), KeyEvent.KEYCODE_UNKNOWN);
KeyEvent receivedKeyEvent = getKeyEvent(RETRY_COUNT);
String log = "Expected " + expectedKeyEvent + " Received " + receivedKeyEvent;
assertNotNull(log, receivedKeyEvent);
assertEquals("DeviceId: " + log, expectedKeyEvent.getDeviceId(),
receivedKeyEvent.getDeviceId());
assertEquals("Action: " + log, expectedKeyEvent.getAction(),
receivedKeyEvent.getAction());
assertEquals("Source: " + log, expectedKeyEvent.getSource(),
receivedKeyEvent.getSource());
assertEquals("KeyCode: " + log, expectedKeyEvent.getKeyCode(),
receivedKeyEvent.getKeyCode());
assertEquals("RepeatCount: " + log, expectedKeyEvent.getRepeatCount(),
receivedKeyEvent.getRepeatCount());
}
/**
* Generate a key event from the key label and action.
* @param action KeyEvent.ACTION_DOWN or KeyEvent.ACTION_UP
* @param label Key label from key layout mapping definition
* @return KeyEvent expected to receive
*/
private KeyEvent generateKeyEvent(int deviceId, int action, String label, int repeat) {
int source = InputDevice.SOURCE_KEYBOARD;
int keyCode = KeyEvent.keyCodeFromString(LABEL_PREFIX + label);
// We will only check select fields of the KeyEvent. Times are not checked.
KeyEvent event = new KeyEvent(/* downTime */ 0, /* eventTime */ 0, action, keyCode,
repeat, /* metaState */ 0, mInputManagerDeviceIds[deviceId], /* scanCode */ 0,
/* flags */ 0, source);
return event;
}
/**
* Simulate pressing a key.
* @param evKeyCode The key scan code
*/
private void pressKeyDown(int deviceId, int evKeyCode) {
int[] evCodesDown = new int[] {
EV_KEY, evKeyCode, EV_KEY_DOWN,
EV_SYN, 0, 0};
mUinputDevices[deviceId].injectEvents(Arrays.toString(evCodesDown));
}
/**
* Simulate releasing a key.
* @param evKeyCode The key scan code
*/
private void pressKeyUp(int deviceId, int evKeyCode) {
int[] evCodesUp = new int[] {
EV_KEY, evKeyCode, EV_KEY_UP,
EV_SYN, 0, 0 };
mUinputDevices[deviceId].injectEvents(Arrays.toString(evCodesUp));
}
private void assertKeyRepeat(int deviceId, String label, int repeat, int count) {
for (int i = 0; i < count; i++) {
KeyEvent expectedDownEvent = generateKeyEvent(deviceId,
KeyEvent.ACTION_DOWN, label, repeat + i);
assertReceivedKeyEvent(expectedDownEvent);
}
}
private void assertKeyUp(int deviceId, String label) {
KeyEvent expectedUpEvent = generateKeyEvent(deviceId,
KeyEvent.ACTION_UP, label, /* repeat */ 0);
assertReceivedKeyEvent(expectedUpEvent);
}
@Test
public void testReceivesKeyRepeatFromTwoDevices() {
final String keyOne = "1";
// Press the key from device 0
pressKeyDown(/* deviceId */ 0, EV_KEY_CODE_1);
// KeyDown repeat driven by device 0
assertKeyRepeat(/* deviceId */ 0, keyOne, /* repeat */ 0, /* count */ 10);
// Press the key from device 1
pressKeyDown(/* deviceId */ 1, EV_KEY_CODE_1);
// KeyDown repeat driven by device 1
assertKeyRepeat(/* deviceId */ 1, keyOne, /* repeat */ 0, /* count */ 10);
}
@Test
public void testReceivesKeyRepeatOnTwoKeysFromTwoDevices() {
final String keyOne = "1";
final String keyTwo = "2";
// Press the key 1 from device 0
pressKeyDown(/* deviceId */ 0, EV_KEY_CODE_1);
// KeyDown repeat driven by device 0
assertKeyRepeat(/* deviceId */ 0, keyOne, /* repeat */ 0, /* count */ 10);
// Press the key 2 from device 1
pressKeyDown(/* deviceId */ 1, EV_KEY_CODE_2);
// KeyDown repeat driven by device 1
assertKeyRepeat(/* deviceId */ 1, keyTwo, /* repeat */ 0, /* count */ 10);
// Release the key 2 from device 1
// Generate expected key up event and verify
pressKeyUp(/* deviceId */ 1, EV_KEY_CODE_2);
assertKeyUp(/* deviceId */ 1, keyTwo);
// No key repeating anymore.
assertNoKeyEvent();
// Release the key 1 from device 0
// Generate expected key up event and verify
pressKeyUp(/* deviceId */ 0, EV_KEY_CODE_1);
assertKeyUp(/* deviceId */ 0, keyOne);
}
@Test
public void testKeyRepeatAfterStaleDeviceKeyUp() {
final String keyOne = "1";
// Press the key from device 0
pressKeyDown(/* deviceId */ 0, EV_KEY_CODE_1);
// KeyDown repeat driven by device 0
assertKeyRepeat(/* deviceId */ 0, keyOne, /* repeat */ 0, /* count */ 10);
// Press the key from device 1
pressKeyDown(/* deviceId */ 1, EV_KEY_CODE_1);
// KeyDown repeat driven by device 1
assertKeyRepeat(/* deviceId */ 1, keyOne, /* repeat */ 0, /* count */ 10);
// Release the key from device 0
// Generate expected key up event and verify
pressKeyUp(/* deviceId */ 0, EV_KEY_CODE_1);
assertKeyUp(/* deviceId */ 0, keyOne);
// KeyDown kept repeating by device 1
assertKeyRepeat(/* deviceId */ 1, keyOne, /* repeat */ 10, /* count */ 10);
// Release the key from device 1
// Generate expected key up event and verify
pressKeyUp(/* deviceId */ 1, EV_KEY_CODE_1);
assertKeyUp(/* deviceId */ 1, keyOne);
}
@Test
public void testKeyRepeatStopsAfterRepeatingKeyUp() {
final String keyOne = "1";
// Press the key from device 0
pressKeyDown(/* deviceId */ 0, EV_KEY_CODE_1);
// KeyDown repeat driven by device 0
assertKeyRepeat(/* deviceId */ 0, keyOne, /* repeat */ 0, /* count */ 10);
// Press the key from device 1
pressKeyDown(/* deviceId */ 1, EV_KEY_CODE_1);
// KeyDown repeat driven by device 1
assertKeyRepeat(/* deviceId */ 1, keyOne, /* repeat */ 0, /* count */ 10);
// Release the key from device 1
// Generate expected key up event and verify
pressKeyUp(/* deviceId */ 1, EV_KEY_CODE_1);
assertKeyUp(/* deviceId */ 1, keyOne);
// No key repeating anymore.
assertNoKeyEvent();
// Release the key from device 0
// Generate expected key up event and verify
pressKeyUp(/* deviceId */ 0, EV_KEY_CODE_1);
assertKeyUp(/* deviceId */ 0, keyOne);
}
}