• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 package com.android.helper.aoa;
17 
18 import static com.android.helper.aoa.AoaDevice.ACCESSORY_REGISTER_HID;
19 import static com.android.helper.aoa.AoaDevice.ACCESSORY_SEND_HID_EVENT;
20 import static com.android.helper.aoa.AoaDevice.ACCESSORY_SET_HID_REPORT_DESC;
21 import static com.android.helper.aoa.AoaDevice.ACCESSORY_START;
22 import static com.android.helper.aoa.AoaDevice.ACCESSORY_UNREGISTER_HID;
23 import static com.android.helper.aoa.AoaDevice.DEVICE_NOT_FOUND;
24 import static com.android.helper.aoa.AoaDevice.FLING_STEPS;
25 import static com.android.helper.aoa.AoaDevice.GOOGLE_VID;
26 import static com.android.helper.aoa.AoaDevice.LONG_CLICK;
27 import static com.android.helper.aoa.AoaDevice.SCROLL_STEPS;
28 import static com.android.helper.aoa.AoaDevice.SYSTEM_BACK;
29 import static com.android.helper.aoa.AoaDevice.SYSTEM_HOME;
30 import static com.android.helper.aoa.AoaDevice.SYSTEM_WAKE;
31 import static com.android.helper.aoa.AoaDevice.TOUCH_DOWN;
32 import static com.android.helper.aoa.AoaDevice.TOUCH_UP;
33 
34 import static org.junit.Assert.assertEquals;
35 import static org.junit.Assert.assertFalse;
36 import static org.junit.Assert.assertTrue;
37 import static org.mockito.ArgumentMatchers.any;
38 import static org.mockito.ArgumentMatchers.anyByte;
39 import static org.mockito.ArgumentMatchers.anyInt;
40 import static org.mockito.ArgumentMatchers.anyString;
41 import static org.mockito.ArgumentMatchers.eq;
42 import static org.mockito.Mockito.atLeastOnce;
43 import static org.mockito.Mockito.clearInvocations;
44 import static org.mockito.Mockito.inOrder;
45 import static org.mockito.Mockito.mock;
46 import static org.mockito.Mockito.never;
47 import static org.mockito.Mockito.spy;
48 import static org.mockito.Mockito.times;
49 import static org.mockito.Mockito.verify;
50 import static org.mockito.Mockito.when;
51 
52 import com.google.common.primitives.Shorts;
53 
54 import org.junit.Before;
55 import org.junit.Test;
56 import org.junit.runner.RunWith;
57 import org.junit.runners.JUnit4;
58 import org.mockito.ArgumentCaptor;
59 import org.mockito.InOrder;
60 import org.mockito.verification.VerificationMode;
61 
62 import java.awt.*;
63 import java.time.Duration;
64 import java.util.Arrays;
65 import java.util.List;
66 import java.util.Objects;
67 import java.util.stream.Collectors;
68 import java.util.stream.Stream;
69 
70 import javax.annotation.Nonnull;
71 
72 /** Unit tests for {@link AoaDevice} */
73 @RunWith(JUnit4.class)
74 public class AoaDeviceTest {
75 
76     private static final int HID_COUNT = AoaDevice.HID.values().length;
77     private static final String SERIAL_NUMBER = "serial-number";
78     private static final int INVALID_VID = 0x0000;
79     private static final int ADB_DISABLED_PID = 0x2D00;
80     private static final int ADB_ENABLED_PID = 0x2D01;
81 
82     private AoaDevice mDevice;
83 
84     private UsbHelper mHelper;
85     private UsbDevice mDelegate;
86 
87     @Before
setUp()88     public void setUp() {
89         // valid accessory mode device by default
90         mDelegate = mock(UsbDevice.class);
91         when(mDelegate.isValid()).thenReturn(true);
92         when(mDelegate.getSerialNumber()).thenReturn(SERIAL_NUMBER);
93         when(mDelegate.getVendorId()).thenReturn(GOOGLE_VID);
94         when(mDelegate.getProductId()).thenReturn(ADB_DISABLED_PID);
95 
96         mHelper = mock(UsbHelper.class);
97         when(mHelper.getDevice(anyString(), any())).thenReturn(mDelegate);
98     }
99 
100     // Initialization
101 
102     @Test
testRegistersHIDsAutomatically()103     public void testRegistersHIDsAutomatically() {
104         mDevice = createDevice();
105 
106         // already in accessory mode
107         verifyRequest(never(), ACCESSORY_START);
108 
109         // registers HIDs
110         verifyRequest(never(), ACCESSORY_UNREGISTER_HID);
111         verifyRequest(times(HID_COUNT), ACCESSORY_REGISTER_HID);
112         verifyRequest(times(HID_COUNT), ACCESSORY_SET_HID_REPORT_DESC);
113     }
114 
115     @Test
testDetectsAccessoryMode()116     public void testDetectsAccessoryMode() {
117         // not in accessory mode initially
118         when(mDelegate.getVendorId())
119                 .thenReturn(INVALID_VID)
120                 .thenReturn(INVALID_VID)
121                 .thenReturn(GOOGLE_VID);
122 
123         mDevice = createDevice();
124 
125         // restarts in accessory mode
126         verifyRequest(times(1), ACCESSORY_START);
127 
128         // registers HIDs afterwards
129         verifyRequest(never(), ACCESSORY_UNREGISTER_HID);
130         verifyRequest(times(HID_COUNT), ACCESSORY_REGISTER_HID);
131         verifyRequest(times(HID_COUNT), ACCESSORY_SET_HID_REPORT_DESC);
132     }
133 
134     @Test
testResetConnection()135     public void testResetConnection() {
136         mDevice = createDevice();
137         clearInvocations(mDelegate);
138         mDevice.resetConnection();
139 
140         verify(mHelper, times(1)).getDevice(eq(SERIAL_NUMBER), any());
141 
142         // re-registers HIDs
143         verifyRequest(times(HID_COUNT), ACCESSORY_UNREGISTER_HID);
144         verifyRequest(times(HID_COUNT), ACCESSORY_REGISTER_HID);
145         verifyRequest(times(HID_COUNT), ACCESSORY_SET_HID_REPORT_DESC);
146     }
147 
148     @Test(expected = UsbException.class)
testThrowsIfConnectionInvalid()149     public void testThrowsIfConnectionInvalid() {
150         when(mDelegate.isValid()).thenReturn(false);
151         mDevice = createDevice();
152     }
153 
154     @Test(expected = UsbException.class)
testThrowsIfSerialNumberMissing()155     public void testThrowsIfSerialNumberMissing() {
156         when(mDelegate.getSerialNumber()).thenReturn(null);
157         mDevice = createDevice();
158     }
159 
160     @Test
testGetSerialNumber()161     public void testGetSerialNumber() {
162         mDevice = createDevice();
163         assertEquals(SERIAL_NUMBER, mDevice.getSerialNumber());
164     }
165 
166     @Test
testIsAdbEnabled()167     public void testIsAdbEnabled() {
168         mDevice = createDevice();
169 
170         // invalid VID and valid PID
171         when(mDelegate.getVendorId()).thenReturn(INVALID_VID);
172         when(mDelegate.getProductId()).thenReturn(ADB_ENABLED_PID);
173         assertFalse(mDevice.isAdbEnabled());
174 
175         // valid VID and invalid PID
176         when(mDelegate.getVendorId()).thenReturn(GOOGLE_VID);
177         when(mDelegate.getProductId()).thenReturn(ADB_DISABLED_PID);
178         assertFalse(mDevice.isAdbEnabled());
179 
180         // both valid
181         when(mDelegate.getVendorId()).thenReturn(GOOGLE_VID);
182         when(mDelegate.getProductId()).thenReturn(ADB_ENABLED_PID);
183         assertTrue(mDevice.isAdbEnabled());
184     }
185 
186     @Test
testClose()187     public void testClose() {
188         mDevice = createDevice();
189         mDevice.close();
190 
191         // unregisters descriptors and closes connection
192         verifyRequest(times(HID_COUNT), ACCESSORY_UNREGISTER_HID);
193         verify(mDelegate, times(1)).close();
194     }
195 
196     // Actions
197 
198     @Test
testClick()199     public void testClick() {
200         mDevice = createDevice();
201         clearInvocations(mDevice);
202         mDevice.click(new Point(12, 34));
203 
204         verifyTouches(new Touch(TOUCH_DOWN, 12, 34), new Touch(TOUCH_UP, 12, 34));
205     }
206 
207     @Test
testLongClick()208     public void testLongClick() {
209         mDevice = createDevice();
210         clearInvocations(mDevice);
211         mDevice.longClick(new Point(23, 45));
212 
213         verifyTouches(new Touch(TOUCH_DOWN, 23, 45), new Touch(TOUCH_UP, 23, 45));
214         verify(mDevice, atLeastOnce()).sleep(LONG_CLICK);
215     }
216 
217     @Test
testScroll()218     public void testScroll() {
219         mDevice = createDevice();
220         mDevice.scroll(new Point(0, 0), new Point(0, SCROLL_STEPS));
221 
222         // generate an event for each step, spaced one pixel apart
223         List<Touch> events =
224                 Stream.iterate(0, i -> i + 1)
225                         .limit(SCROLL_STEPS + 1)
226                         .map(i -> new Touch(TOUCH_DOWN, 0, i))
227                         .collect(Collectors.toList());
228         events.add(new Touch(TOUCH_UP, 0, SCROLL_STEPS));
229 
230         verifyTouches(events);
231     }
232 
233     @Test
testFling()234     public void testFling() {
235         mDevice = createDevice();
236         mDevice.fling(new Point(0, 0), new Point(FLING_STEPS, 0));
237 
238         // generate an event for each step, spaced one pixel apart
239         List<Touch> events =
240                 Stream.iterate(0, i -> i + 1)
241                         .limit(FLING_STEPS + 1)
242                         .map(i -> new Touch(TOUCH_DOWN, i, 0))
243                         .collect(Collectors.toList());
244         events.add(new Touch(TOUCH_UP, FLING_STEPS, 0));
245 
246         verifyTouches(events);
247     }
248 
249     @Test
testDrag()250     public void testDrag() {
251         mDevice = createDevice();
252         mDevice.drag(new Point(0, 0), new Point(SCROLL_STEPS, 0));
253 
254         // generate an event for each step, spaced one pixel apart
255         List<Touch> events =
256                 Stream.iterate(0, i -> i + 1)
257                         .limit(SCROLL_STEPS + 1)
258                         .map(i -> new Touch(TOUCH_DOWN, i, 0))
259                         .collect(Collectors.toList());
260         // drag is a long click followed by a scroll
261         events.add(0, new Touch(TOUCH_DOWN, 0, 0));
262         events.add(new Touch(TOUCH_UP, SCROLL_STEPS, 0));
263 
264         verifyTouches(events);
265     }
266 
267     @Test
testWrite()268     public void testWrite() {
269         mDevice = spy(createDevice());
270         mDevice.write("Test #0123!");
271 
272         verify(mDevice).key(0x17, 0x08, 0x16, 0x17, 0x2C, null, 0x27, 0x1E, 0x1F, 0x20, null);
273     }
274 
275     @Test
testKey()276     public void testKey() {
277         mDevice = createDevice();
278         mDevice.key(1, null, 2);
279 
280         InOrder order = inOrder(mDelegate);
281         // press and release 1
282         verifyHidRequest(order, times(1), AoaDevice.HID.KEYBOARD, (byte) 1);
283         verifyHidRequest(order, times(1), AoaDevice.HID.KEYBOARD, (byte) 0);
284         // skip null, and press and release 2
285         verifyHidRequest(order, times(1), AoaDevice.HID.KEYBOARD, (byte) 2);
286         verifyHidRequest(order, times(1), AoaDevice.HID.KEYBOARD, (byte) 0);
287     }
288 
289     @Test
testWakeUp()290     public void testWakeUp() {
291         mDevice = createDevice();
292         mDevice.wakeUp();
293 
294         verifyHidRequest(times(1), AoaDevice.HID.SYSTEM, SYSTEM_WAKE);
295     }
296 
297     @Test
testGoHome()298     public void testGoHome() {
299         mDevice = createDevice();
300         mDevice.goHome();
301 
302         verifyHidRequest(times(1), AoaDevice.HID.SYSTEM, SYSTEM_HOME);
303     }
304 
305     @Test
testGoBack()306     public void testGoBack() {
307         mDevice = createDevice();
308         mDevice.goBack();
309 
310         verifyHidRequest(times(1), AoaDevice.HID.SYSTEM, SYSTEM_BACK);
311     }
312 
313     @Test
testRetryAction()314     public void testRetryAction() {
315         mDevice = createDevice();
316 
317         // first attempt will fail to find device
318         when(mDelegate.controlTransfer(anyByte(), anyByte(), anyInt(), anyInt(), any()))
319                 .thenReturn(DEVICE_NOT_FOUND)
320                 .thenReturn(0);
321 
322         mDevice.click(new Point(34, 56));
323 
324         // reset the connection
325         verify(mHelper, times(1)).getDevice(eq(SERIAL_NUMBER), any());
326 
327         verifyTouches(
328                 new Touch(TOUCH_DOWN, 34, 56), // failed
329                 new Touch(TOUCH_DOWN, 34, 56), // retry after resetting connection
330                 new Touch(TOUCH_UP, 34, 56));
331     }
332 
333     // Helpers
334 
createDevice()335     private AoaDevice createDevice() {
336         AoaDevice device = new AoaDevice(mHelper, mDelegate) {
337             @Override
338             public void sleep(@Nonnull Duration duration) {}
339         };
340         return spy(device);
341     }
342 
verifyRequest(VerificationMode mode, byte request)343     private void verifyRequest(VerificationMode mode, byte request) {
344         verify(mDelegate, mode)
345                 .controlTransfer(anyByte(), eq(request), anyInt(), anyInt(), any());
346     }
347 
verifyHidRequest(VerificationMode mode, AoaDevice.HID hid, byte... data)348     private void verifyHidRequest(VerificationMode mode, AoaDevice.HID hid, byte... data) {
349         verifyHidRequest(null, mode, hid, data);
350     }
351 
verifyHidRequest( InOrder order, VerificationMode mode, AoaDevice.HID hid, byte... data)352     private void verifyHidRequest(
353             InOrder order, VerificationMode mode, AoaDevice.HID hid, byte... data) {
354         UsbDevice verifier =
355                 order == null ? verify(mDelegate, mode) : order.verify(mDelegate, mode);
356         verifier.controlTransfer(
357                 anyByte(), eq(ACCESSORY_SEND_HID_EVENT), eq(hid.getId()), anyInt(), eq(data));
358     }
359 
verifyTouches(Touch... expected)360     private void verifyTouches(Touch... expected) {
361         verifyTouches(Arrays.asList(expected));
362     }
363 
verifyTouches(List<Touch> expected)364     private void verifyTouches(List<Touch> expected) {
365         ArgumentCaptor<byte[]> captor = ArgumentCaptor.forClass(byte[].class);
366 
367         verify(mDelegate, times(expected.size()))
368                 .controlTransfer(
369                         anyByte(),
370                         eq(ACCESSORY_SEND_HID_EVENT),
371                         eq(AoaDevice.HID.TOUCH_SCREEN.getId()),
372                         anyInt(),
373                         captor.capture());
374 
375         List<Touch> events =
376                 captor.getAllValues().stream().map(Touch::new).collect(Collectors.toList());
377         assertEquals(expected, events);
378     }
379 
380     /** Touch HID event. */
381     private static class Touch {
382 
383         private final byte mType;
384         private final int mX;
385         private final int mY;
386 
Touch(byte type, int x, int y)387         private Touch(byte type, int x, int y) {
388             mType = type;
389             mX = x;
390             mY = y;
391         }
392 
Touch(byte[] data)393         private Touch(byte[] data) {
394             mType = data[0];
395             mX = Shorts.fromBytes(data[2], data[1]);
396             mY = Shorts.fromBytes(data[4], data[3]);
397         }
398 
399         @Override
equals(Object object)400         public boolean equals(Object object) {
401             if (this == object) {
402                 return true;
403             }
404             if (!(object instanceof Touch)) {
405                 return false;
406             }
407             Touch event = (Touch) object;
408             return mType == event.mType && mX == event.mX && mY == event.mY;
409         }
410 
411         @Override
hashCode()412         public int hashCode() {
413             return Objects.hash(mType, mX, mY);
414         }
415 
416         @Override
toString()417         public String toString() {
418             return String.format("Touch{%d, %d, %d}", mType, mX, mY);
419         }
420     }
421 }
422