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