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