1 /* 2 * Copyright 2020 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 android.hardware.input.cts.tests; 18 19 import static android.hardware.lights.LightsRequest.Builder; 20 21 import static com.google.common.truth.Truth.assertThat; 22 23 import static org.junit.Assert.assertEquals; 24 import static org.junit.Assert.assertFalse; 25 import static org.junit.Assert.assertNotNull; 26 import static org.junit.Assert.assertTrue; 27 import static org.junit.Assert.fail; 28 import static org.mockito.Mockito.reset; 29 import static org.mockito.Mockito.timeout; 30 import static org.mockito.Mockito.verify; 31 32 import android.hardware.BatteryState; 33 import android.hardware.input.InputManager; 34 import android.hardware.input.cts.GlobalKeyMapping; 35 import android.hardware.lights.Light; 36 import android.hardware.lights.LightState; 37 import android.hardware.lights.LightsManager; 38 import android.os.SystemClock; 39 import android.os.VibrationEffect; 40 import android.os.Vibrator; 41 import android.os.Vibrator.OnVibratorStateChangedListener; 42 import android.os.VintfRuntimeInfo; 43 import android.text.TextUtils; 44 import android.util.Log; 45 import android.util.Pair; 46 import android.view.InputDevice; 47 48 import androidx.annotation.CallSuper; 49 50 import com.android.cts.input.HidBatteryTestData; 51 import com.android.cts.input.HidDevice; 52 import com.android.cts.input.HidLightTestData; 53 import com.android.cts.input.HidResultData; 54 import com.android.cts.input.HidTestData; 55 import com.android.cts.input.HidVibratorTestData; 56 57 import org.junit.Rule; 58 import org.mockito.Mock; 59 import org.mockito.junit.MockitoJUnit; 60 import org.mockito.junit.MockitoRule; 61 62 import java.util.ArrayList; 63 import java.util.Arrays; 64 import java.util.List; 65 import java.util.Map; 66 import java.util.regex.Matcher; 67 import java.util.regex.Pattern; 68 69 public class InputHidTestCase extends InputTestCase { 70 private static final String TAG = "InputHidTestCase"; 71 // Sync with linux uhid_event_type::UHID_OUTPUT 72 private static final byte UHID_EVENT_TYPE_UHID_OUTPUT = 6; 73 private static final long CALLBACK_TIMEOUT_MILLIS = 5000; 74 75 private HidDevice mHidDevice; 76 private final GlobalKeyMapping mGlobalKeyMapping = new GlobalKeyMapping( 77 mInstrumentation.getTargetContext()); 78 private int mDeviceId; 79 private final int mRegisterResourceId; 80 private boolean mDelayAfterSetup = false; 81 82 @Rule 83 public MockitoRule rule = MockitoJUnit.rule(); 84 @Mock 85 private OnVibratorStateChangedListener mListener; 86 InputHidTestCase(int registerResourceId)87 InputHidTestCase(int registerResourceId) { 88 super(registerResourceId); 89 mRegisterResourceId = registerResourceId; 90 } 91 92 @CallSuper 93 @Override setUp()94 public void setUp() throws Exception { 95 super.setUp(); 96 // Even though we already wait for all possible callbacks such as UHID_START and UHID_OPEN, 97 // and wait for the correct device to appear by specifying expected source type in the 98 // register command, some devices, perhaps due to splitting, do not produce events as soon 99 // as they are created. Adding a small delay resolves this issue. 100 if (mDelayAfterSetup) { 101 SystemClock.sleep(1000); 102 } 103 } 104 addDelayAfterSetup()105 protected void addDelayAfterSetup() { 106 mDelayAfterSetup = true; 107 } 108 109 /** Check if input device has specific capability */ 110 interface Capability { check(InputDevice inputDevice)111 boolean check(InputDevice inputDevice); 112 } 113 getVersionFromString(String version)114 private static Pair<Integer, Integer> getVersionFromString(String version) { 115 // Only gets major and minor number of the version string. 116 final Pattern versionPattern = Pattern.compile("^(\\d+)(\\.(\\d+))?.*"); 117 final Matcher m = versionPattern.matcher(version); 118 if (m.matches()) { 119 final int major = Integer.parseInt(m.group(1)); 120 final int minor = TextUtils.isEmpty(m.group(3)) ? 0 : Integer.parseInt(m.group(3)); 121 122 return new Pair<>(major, minor); 123 124 } else { 125 fail("Cannot parse kernel version: " + version); 126 return new Pair<>(0, 0); 127 } 128 } 129 compareMajorMinorVersion(final String s1, final String s2)130 private static int compareMajorMinorVersion(final String s1, final String s2) throws Exception { 131 final Pair<Integer, Integer> v1 = getVersionFromString(s1); 132 final Pair<Integer, Integer> v2 = getVersionFromString(s2); 133 134 if (v1.first == v2.first) { 135 return Integer.compare(v1.second, v2.second); 136 } else { 137 return Integer.compare(v1.first, v2.first); 138 } 139 } 140 isKernelVersionGreaterThan(String version)141 protected static boolean isKernelVersionGreaterThan(String version) throws Exception { 142 final String actualVersion = VintfRuntimeInfo.getKernelVersion(); 143 return compareMajorMinorVersion(actualVersion, version) > 0; 144 } 145 146 /** Gets an input device with specific capability */ getInputDevice(Capability capability)147 private InputDevice getInputDevice(Capability capability) { 148 final InputManager inputManager = 149 mInstrumentation.getTargetContext().getSystemService(InputManager.class); 150 final int[] inputDeviceIds = inputManager.getInputDeviceIds(); 151 for (int inputDeviceId : inputDeviceIds) { 152 final InputDevice inputDevice = inputManager.getInputDevice(inputDeviceId); 153 if (inputDevice.getVendorId() == mVid && inputDevice.getProductId() == mPid 154 && capability.check(inputDevice)) { 155 return inputDevice; 156 } 157 } 158 return null; 159 } 160 161 /** 162 * Gets a vibrator from input device with specified Vendor Id and Product Id 163 * from device registration command. 164 * @return Vibrator object in specified InputDevice 165 */ getVibrator()166 private Vibrator getVibrator() { 167 InputDevice inputDevice = getInputDevice((d) -> d.getVibrator().hasVibrator()); 168 if (inputDevice == null) { 169 fail("Failed to find test device with vibrator"); 170 } 171 return inputDevice.getVibrator(); 172 } 173 174 /** 175 * Gets a battery from input device with specified Vendor Id and Product Id 176 * from device registration command. 177 * @return Battery object in specified InputDevice 178 */ getBatteryState()179 private BatteryState getBatteryState() { 180 InputDevice inputDevice = getInputDevice((d) -> d.getBatteryState().isPresent()); 181 if (inputDevice == null) { 182 fail("Failed to find test device with battery"); 183 } 184 return inputDevice.getBatteryState(); 185 } 186 187 /** 188 * Gets a light manager object from input device with specified Vendor Id and Product Id 189 * from device registration command. 190 * @return LightsManager object in specified InputDevice 191 */ getLightsManager()192 private LightsManager getLightsManager() { 193 InputDevice inputDevice = getInputDevice( 194 (d) -> !d.getLightsManager().getLights().isEmpty()); 195 if (inputDevice == null) { 196 fail("Failed to find test device with light"); 197 } 198 return inputDevice.getLightsManager(); 199 } 200 201 @Override setUpDevice(int id, int vendorId, int productId, int sources, String registerCommand)202 protected void setUpDevice(int id, int vendorId, int productId, int sources, 203 String registerCommand) { 204 mDeviceId = id; 205 mHidDevice = new HidDevice(mInstrumentation, id, vendorId, productId, sources, 206 registerCommand); 207 assertNotNull(mHidDevice); 208 } 209 210 @Override tearDownDevice()211 protected void tearDownDevice() { 212 if (mHidDevice != null) { 213 mHidDevice.close(); 214 } 215 } 216 217 @Override testInputDeviceEvents(int resourceId)218 protected void testInputDeviceEvents(int resourceId) { 219 List<HidTestData> tests = mParser.getHidTestData(resourceId); 220 // Global keys are handled by the framework and do not reach apps. 221 // The set of global keys is vendor-specific. 222 // Remove tests which contain global keys because we can't test them 223 tests.removeIf(testData -> testData.events.removeIf(mGlobalKeyMapping::isGlobalKey)); 224 225 for (HidTestData testData: tests) { 226 mCurrentTestCase = testData.name; 227 // Send all of the HID reports 228 for (int i = 0; i < testData.reports.size(); i++) { 229 final String report = testData.reports.get(i); 230 mHidDevice.sendHidReport(report); 231 } 232 verifyEvents(testData.events); 233 } 234 } 235 verifyVibratorReportData(HidVibratorTestData test, HidResultData result)236 private boolean verifyVibratorReportData(HidVibratorTestData test, HidResultData result) { 237 for (Map.Entry<Integer, Integer> entry : test.verifyMap.entrySet()) { 238 final int index = entry.getKey(); 239 final int value = entry.getValue(); 240 if ((result.reportData[index] & 0XFF) != value) { 241 Log.v(TAG, "index=" + index + " value= " + value 242 + "actual= " + (result.reportData[index] & 0XFF)); 243 return false; 244 } 245 } 246 final int ffLeft = result.reportData[test.leftFfIndex] & 0xFF; 247 final int ffRight = result.reportData[test.rightFfIndex] & 0xFF; 248 249 return ffLeft > 0 && ffRight > 0; 250 } 251 testInputVibratorEvents(int resourceId)252 public void testInputVibratorEvents(int resourceId) throws Exception { 253 final List<HidVibratorTestData> tests = mParser.getHidVibratorTestData(resourceId); 254 255 for (HidVibratorTestData test : tests) { 256 assertEquals(test.durations.size(), test.amplitudes.size()); 257 assertTrue(test.durations.size() > 0); 258 259 final long timeoutMills; 260 final long totalVibrations = test.durations.size(); 261 final VibrationEffect effect; 262 if (test.durations.size() == 1) { 263 long duration = test.durations.get(0); 264 int amplitude = test.amplitudes.get(0); 265 effect = VibrationEffect.createOneShot(duration, amplitude); 266 // Set timeout to be 2 times of the effect duration. 267 timeoutMills = duration * 2; 268 } else { 269 long[] durations = test.durations.stream().mapToLong(Long::longValue).toArray(); 270 int[] amplitudes = test.amplitudes.stream().mapToInt(Integer::intValue).toArray(); 271 effect = VibrationEffect.createWaveform( 272 durations, amplitudes, -1); 273 // Set timeout to be 2 times of the effect total duration. 274 timeoutMills = Arrays.stream(durations).sum() * 2; 275 } 276 277 final Vibrator vibrator = getVibrator(); 278 assertNotNull(vibrator); 279 vibrator.addVibratorStateListener(mListener); 280 verify(mListener, timeout(CALLBACK_TIMEOUT_MILLIS) 281 .times(1)).onVibratorStateChanged(false); 282 reset(mListener); 283 // Start vibration 284 vibrator.vibrate(effect); 285 // Verify vibrator state listener 286 verify(mListener, timeout(CALLBACK_TIMEOUT_MILLIS) 287 .times(1)).onVibratorStateChanged(true); 288 assertTrue(vibrator.isVibrating()); 289 290 final long startTime = SystemClock.elapsedRealtime(); 291 List<HidResultData> results = new ArrayList<>(); 292 int vibrationCount = 0; 293 // Check the vibration ffLeft and ffRight amplitude to be expected. 294 while (vibrationCount < totalVibrations 295 && SystemClock.elapsedRealtime() - startTime < timeoutMills) { 296 SystemClock.sleep(1000); 297 298 results = mHidDevice.getResults(mDeviceId, UHID_EVENT_TYPE_UHID_OUTPUT); 299 if (results.size() < totalVibrations) { 300 continue; 301 } 302 vibrationCount = 0; 303 for (int i = 0; i < results.size(); i++) { 304 HidResultData result = results.get(i); 305 if (result.deviceId == mDeviceId 306 && verifyVibratorReportData(test, result)) { 307 int ffLeft = result.reportData[test.leftFfIndex] & 0xFF; 308 int ffRight = result.reportData[test.rightFfIndex] & 0xFF; 309 Log.v(TAG, "eventId=" + result.eventId + " reportType=" 310 + result.reportType + " left=" + ffLeft + " right=" + ffRight); 311 // Check the amplitudes of FF effect are expected. 312 if (ffLeft == test.amplitudes.get(vibrationCount) 313 && ffRight == test.amplitudes.get(vibrationCount)) { 314 vibrationCount++; 315 } 316 } 317 } 318 } 319 assertEquals(vibrationCount, totalVibrations); 320 // Verify vibrator state listener 321 verify(mListener, timeout(CALLBACK_TIMEOUT_MILLIS) 322 .times(1)).onVibratorStateChanged(false); 323 assertFalse(vibrator.isVibrating()); 324 vibrator.removeVibratorStateListener(mListener); 325 reset(mListener); 326 } 327 } 328 testInputVibratorManagerEvents(int resourceId)329 public void testInputVibratorManagerEvents(int resourceId) throws Exception { 330 final List<HidVibratorTestData> tests = mParser.getHidVibratorTestData(resourceId); 331 332 for (HidVibratorTestData test : tests) { 333 assertEquals(test.durations.size(), test.amplitudes.size()); 334 assertTrue(test.durations.size() > 0); 335 336 final long timeoutMills; 337 final long totalVibrations = test.durations.size(); 338 final VibrationEffect effect; 339 if (test.durations.size() == 1) { 340 long duration = test.durations.get(0); 341 int amplitude = test.amplitudes.get(0); 342 effect = VibrationEffect.createOneShot(duration, amplitude); 343 // Set timeout to be 2 times of the effect duration. 344 timeoutMills = duration * 2; 345 } else { 346 long[] durations = test.durations.stream().mapToLong(Long::longValue).toArray(); 347 int[] amplitudes = test.amplitudes.stream().mapToInt(Integer::intValue).toArray(); 348 effect = VibrationEffect.createWaveform( 349 durations, amplitudes, -1); 350 // Set timeout to be 2 times of the effect total duration. 351 timeoutMills = Arrays.stream(durations).sum() * 2; 352 } 353 354 final Vibrator vibrator = getVibrator(); 355 assertNotNull(vibrator); 356 // Start vibration 357 vibrator.vibrate(effect); 358 final long startTime = SystemClock.elapsedRealtime(); 359 List<HidResultData> results = new ArrayList<>(); 360 int vibrationCount = 0; 361 // Check the vibration ffLeft and ffRight amplitude to be expected. 362 while (vibrationCount < totalVibrations 363 && SystemClock.elapsedRealtime() - startTime < timeoutMills) { 364 SystemClock.sleep(1000); 365 366 results = mHidDevice.getResults(mDeviceId, UHID_EVENT_TYPE_UHID_OUTPUT); 367 if (results.size() < totalVibrations) { 368 continue; 369 } 370 vibrationCount = 0; 371 for (int i = 0; i < results.size(); i++) { 372 HidResultData result = results.get(i); 373 if (result.deviceId == mDeviceId 374 && verifyVibratorReportData(test, result)) { 375 int ffLeft = result.reportData[test.leftFfIndex] & 0xFF; 376 int ffRight = result.reportData[test.rightFfIndex] & 0xFF; 377 Log.v(TAG, "eventId=" + result.eventId + " reportType=" 378 + result.reportType + " left=" + ffLeft + " right=" + ffRight); 379 // Check the amplitudes of FF effect are expected. 380 if (ffLeft == test.amplitudes.get(vibrationCount) 381 && ffRight == test.amplitudes.get(vibrationCount)) { 382 vibrationCount++; 383 } 384 } 385 } 386 } 387 assertEquals(vibrationCount, totalVibrations); 388 } 389 } 390 testInputBatteryEvents(int resourceId)391 public void testInputBatteryEvents(int resourceId) { 392 final BatteryState batteryState = getBatteryState(); 393 assertNotNull(batteryState); 394 395 final List<HidBatteryTestData> tests = mParser.getHidBatteryTestData(resourceId); 396 for (HidBatteryTestData testData : tests) { 397 398 // Send all of the HID reports 399 for (int i = 0; i < testData.reports.size(); i++) { 400 final String report = testData.reports.get(i); 401 mHidDevice.sendHidReport(report); 402 } 403 // Wait for power_supply sysfs node get updated. 404 SystemClock.sleep(100); 405 float capacity = batteryState.getCapacity(); 406 int status = batteryState.getStatus(); 407 assertEquals("Test: " + testData.name, testData.status, status); 408 boolean capacityMatch = false; 409 for (int i = 0; i < testData.capacities.length; i++) { 410 if (capacity == testData.capacities[i]) { 411 capacityMatch = true; 412 break; 413 } 414 } 415 assertTrue("Test: " + testData.name + " capacity " + capacity + " expect " 416 + Arrays.toString(testData.capacities), capacityMatch); 417 } 418 } 419 testInputLightsManager(int resourceId)420 public void testInputLightsManager(int resourceId) throws Exception { 421 final LightsManager lightsManager = getLightsManager(); 422 final List<Light> lights = lightsManager.getLights(); 423 424 final List<HidLightTestData> tests = mParser.getHidLightTestData(resourceId); 425 for (HidLightTestData test : tests) { 426 Light light = null; 427 for (int i = 0; i < lights.size(); i++) { 428 if (lights.get(i).getType() == test.lightType 429 && test.lightName.equals(lights.get(i).getName())) { 430 light = lights.get(i); 431 } 432 } 433 assertNotNull("Light type " + test.lightType + " name " + test.lightName 434 + " does not exist. Lights found: " + lights, light); 435 try (LightsManager.LightsSession session = lightsManager.openSession()) { 436 // Can't set both player id and color in same LightState 437 assertFalse(test.lightColor > 0 && test.lightPlayerId > 0); 438 // Issue the session requests to turn single light on 439 if (test.lightPlayerId > 0) { 440 session.requestLights(new Builder() 441 .addLight(light, (new LightState.Builder()) 442 .setPlayerId(test.lightPlayerId).build()).build()); 443 } else { 444 session.requestLights(new Builder() 445 .addLight(light, (new LightState.Builder()).setColor(test.lightColor) 446 .build()).build()); 447 } 448 // Some devices (e.g. Sixaxis) defer sending output packets until they've seen at 449 // least one input packet. 450 if (!test.report.isEmpty()) { 451 mHidDevice.sendHidReport(test.report); 452 } 453 // Delay before sysfs node was updated. 454 SystemClock.sleep(200); 455 // Verify HID report data 456 List<HidResultData> results = mHidDevice.getResults(mDeviceId, 457 test.hidEventType); 458 assertFalse(results.isEmpty()); 459 // We just check the last HID output to be expected. 460 HidResultData result = results.get(results.size() - 1); 461 for (Map.Entry<Integer, Integer> entry : test.expectedHidData.entrySet()) { 462 final int index = entry.getKey(); 463 final int value = entry.getValue(); 464 int actual = result.reportData[index] & 0xFF; 465 assertEquals("Led data index " + index, value, actual); 466 467 } 468 469 // Then the light state should be what we requested. 470 if (test.lightPlayerId > 0) { 471 assertThat(lightsManager.getLightState(light).getPlayerId()) 472 .isEqualTo(test.lightPlayerId); 473 } else { 474 assertThat(lightsManager.getLightState(light).getColor()) 475 .isEqualTo(test.lightColor); 476 } 477 } 478 } 479 } 480 481 } 482