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