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