• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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