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