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