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