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