1 /*
2  * Copyright (C) 2016 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 com.android.cts.verifier.usb.device;
18 
19 import static com.android.cts.verifier.usb.Util.runAndAssertException;
20 
21 import static org.junit.Assert.assertArrayEquals;
22 import static org.junit.Assert.assertEquals;
23 import static org.junit.Assert.assertFalse;
24 import static org.junit.Assert.assertNotNull;
25 import static org.junit.Assert.assertNull;
26 import static org.junit.Assert.assertSame;
27 import static org.junit.Assert.assertTrue;
28 
29 import android.app.PendingIntent;
30 import android.content.BroadcastReceiver;
31 import android.content.Context;
32 import android.content.Intent;
33 import android.content.IntentFilter;
34 import android.hardware.usb.UsbConfiguration;
35 import android.hardware.usb.UsbConstants;
36 import android.hardware.usb.UsbDevice;
37 import android.hardware.usb.UsbDeviceConnection;
38 import android.hardware.usb.UsbEndpoint;
39 import android.hardware.usb.UsbInterface;
40 import android.hardware.usb.UsbManager;
41 import android.hardware.usb.UsbRequest;
42 import android.os.Build;
43 import android.os.Bundle;
44 import android.util.ArraySet;
45 import android.util.Log;
46 import android.util.Pair;
47 import android.view.View;
48 import android.widget.ProgressBar;
49 import android.widget.TextView;
50 
51 import androidx.annotation.NonNull;
52 import androidx.annotation.Nullable;
53 
54 import com.android.cts.verifier.PassFailButtons;
55 import com.android.cts.verifier.R;
56 
57 import java.nio.BufferOverflowException;
58 import java.nio.ByteBuffer;
59 import java.nio.CharBuffer;
60 import java.nio.charset.Charset;
61 import java.util.ArrayList;
62 import java.util.HashMap;
63 import java.util.LinkedList;
64 import java.util.Map;
65 import java.util.NoSuchElementException;
66 import java.util.Random;
67 import java.util.Set;
68 import java.util.concurrent.CompletableFuture;
69 import java.util.concurrent.TimeUnit;
70 import java.util.concurrent.TimeoutException;
71 import java.util.concurrent.atomic.AtomicInteger;
72 
73 public class UsbDeviceTestActivity extends PassFailButtons.Activity {
74     private static final String ACTION_USB_PERMISSION =
75             "com.android.cts.verifier.usb.device.USB_PERMISSION";
76     private static final String LOG_TAG = UsbDeviceTestActivity.class.getSimpleName();
77     private static final int TIMEOUT_MILLIS = 5000;
78     private static final int LARGE_BUFFER_SIZE = 124619;
79 
80     private UsbManager mUsbManager;
81     private BroadcastReceiver mUsbDeviceConnectionReceiver;
82     private BroadcastReceiver mUsbDeviceAttachedReceiver;
83     private BroadcastReceiver mUsbDevicePermissionReceiver;
84     private Thread mTestThread;
85     private TextView mStatus;
86     private ProgressBar mProgress;
87     private UsbDevice mDevice;
88 
89     /**
90      * Some N and older accessories do not send a zero sized package after a request that is a
91      * multiple of the maximum package size.
92      */
93     private boolean mDoesCompanionZeroTerminate;
94 
now()95     private static long now() {
96         return System.nanoTime() / 1000000;
97     }
98 
99     /**
100      * Check if we should expect a zero sized transfer after a certain sized transfer
101      *
102      * @param transferSize The size of the previous transfer
103      *
104      * @return {@code true} if a zero sized transfer is expected
105      */
isZeroTransferExpected(int transferSize, @NonNull UsbEndpoint ep)106     private boolean isZeroTransferExpected(int transferSize, @NonNull UsbEndpoint ep) {
107         return mDoesCompanionZeroTerminate && transferSize % ep.getMaxPacketSize() == 0;
108     }
109 
110     @Override
onCreate(Bundle savedInstanceState)111     protected void onCreate(Bundle savedInstanceState) {
112         super.onCreate(savedInstanceState);
113 
114         setContentView(R.layout.usb_main);
115         setInfoResources(R.string.usb_device_test, R.string.usb_device_test_info, -1);
116 
117         mStatus = (TextView) findViewById(R.id.status);
118         mProgress = (ProgressBar) findViewById(R.id.progress_bar);
119 
120         mUsbManager = getSystemService(UsbManager.class);
121 
122         getPassButton().setEnabled(false);
123 
124         IntentFilter filter = new IntentFilter();
125         filter.addAction(ACTION_USB_PERMISSION);
126         filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
127 
128         mStatus.setText(R.string.usb_device_test_step1);
129 
130         mUsbDeviceConnectionReceiver = new BroadcastReceiver() {
131             @Override
132             public void onReceive(Context context, Intent intent) {
133                 synchronized (UsbDeviceTestActivity.this) {
134                     UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
135 
136                     switch (intent.getAction()) {
137                         case UsbManager.ACTION_USB_DEVICE_ATTACHED:
138                             if (!AoapInterface.isDeviceInAoapMode(device)) {
139                                 mStatus.setText(R.string.usb_device_test_step2);
140                             }
141 
142                             if (getApplicationContext().getApplicationInfo().targetSdkVersion
143                                     >= Build.VERSION_CODES.Q) {
144                                 try {
145                                     device.getSerialNumber();
146                                     fail("Serial number could be read", null);
147                                     return;
148                                 } catch (SecurityException expected) {
149                                     // expected as app cannot read serial number without permission
150                                 }
151                             }
152 
153                             mUsbManager.requestPermission(device,
154                                     PendingIntent.getBroadcast(UsbDeviceTestActivity.this, 0,
155                                             new Intent(ACTION_USB_PERMISSION)
156                                                     .setPackage(UsbDeviceTestActivity.this
157                                                             .getPackageName()),
158                                             PendingIntent.FLAG_MUTABLE));
159                             break;
160                         case ACTION_USB_PERMISSION:
161                             boolean granted = intent.getBooleanExtra(
162                                     UsbManager.EXTRA_PERMISSION_GRANTED, false);
163                             if (granted) {
164                                 if (!AoapInterface.isDeviceInAoapMode(device)) {
165                                     mStatus.setText(R.string.usb_device_test_step3);
166 
167                                     UsbDeviceConnection connection = mUsbManager.openDevice(device);
168                                     try {
169                                         makeThisDeviceAnAccessory(connection);
170                                     } finally {
171                                         connection.close();
172                                     }
173                                 } else {
174                                     mStatus.setText(R.string.usb_device_test_step4);
175                                     mProgress.setIndeterminate(true);
176                                     mProgress.setVisibility(View.VISIBLE);
177 
178                                     unregisterReceiver(mUsbDeviceConnectionReceiver);
179                                     mUsbDeviceConnectionReceiver = null;
180 
181                                     // Do not run test on main thread
182                                     mTestThread = new Thread() {
183                                         @Override
184                                         public void run() {
185                                             runTests(device);
186                                         }
187                                     };
188 
189                                     mTestThread.start();
190                                 }
191                             } else {
192                                 fail("Permission to connect to " + device.getProductName()
193                                         + " not granted", null);
194                             }
195                             break;
196                     }
197                 }
198             }
199         };
200         registerReceiver(mUsbDeviceConnectionReceiver, filter, Context.RECEIVER_EXPORTED);
201     }
202 
203     /**
204      * Indicate that the test failed.
205      */
fail(@ullable String s, @Nullable Throwable e)206     private void fail(@Nullable String s, @Nullable Throwable e) {
207         Log.e(LOG_TAG, s, e);
208         setTestResultAndFinish(false);
209     }
210 
211     /**
212      * Converts the device under test into an Android accessory. Accessories are USB hosts that are
213      * detected on the device side via {@link UsbManager#getAccessoryList()}.
214      *
215      * @param connection The connection to the USB device
216      */
makeThisDeviceAnAccessory(@onNull UsbDeviceConnection connection)217     private void makeThisDeviceAnAccessory(@NonNull UsbDeviceConnection connection) {
218         AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_MANUFACTURER,
219                 "Android CTS");
220         AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_MODEL,
221                 "Android device under CTS test");
222         AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_DESCRIPTION,
223                 "Android device running CTS verifier");
224         AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_VERSION, "2");
225         AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_URI,
226                 "https://source.android.com/compatibility/cts/verifier.html");
227         AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_SERIAL, "0");
228         AoapInterface.sendAoapStart(connection);
229     }
230 
231     /**
232      * Switch to next test.
233      *
234      * @param connection   Connection to the USB device
235      * @param in           The in endpoint
236      * @param out          The out endpoint
237      * @param nextTestName The name of the new test
238      */
nextTest(@onNull UsbDeviceConnection connection, @NonNull UsbEndpoint in, @NonNull UsbEndpoint out, @NonNull CharSequence nextTestName)239     private void nextTest(@NonNull UsbDeviceConnection connection, @NonNull UsbEndpoint in,
240             @NonNull UsbEndpoint out, @NonNull CharSequence nextTestName) {
241         Log.v(LOG_TAG, "Finishing previous test");
242 
243         // Make sure name length is not a multiple of 8 to avoid zero-termination issues
244         StringBuilder safeNextTestName = new StringBuilder(nextTestName);
245         if (nextTestName.length() % 8 == 0) {
246             safeNextTestName.append(' ');
247         }
248 
249         // Send name of next test
250         assertTrue(safeNextTestName.length() <= Byte.MAX_VALUE);
251         ByteBuffer nextTestNameBuffer = Charset.forName("UTF-8")
252                 .encode(CharBuffer.wrap(safeNextTestName));
253         byte[] sizeBuffer = { (byte) nextTestNameBuffer.limit() };
254         int numSent = connection.bulkTransfer(out, sizeBuffer, 1, 0);
255         assertEquals(1, numSent);
256 
257         numSent = connection.bulkTransfer(out, nextTestNameBuffer.array(),
258                 nextTestNameBuffer.limit(), 0);
259         assertEquals(nextTestNameBuffer.limit(), numSent);
260 
261         // Receive result of last test
262         byte[] lastTestResultBytes = new byte[1];
263         int numReceived = connection.bulkTransfer(in, lastTestResultBytes,
264                 lastTestResultBytes.length, TIMEOUT_MILLIS);
265         assertEquals(1, numReceived);
266         assertEquals(1, lastTestResultBytes[0]);
267 
268         // Send ready signal
269         sizeBuffer[0] = 42;
270         numSent = connection.bulkTransfer(out, sizeBuffer, 1, 0);
271         assertEquals(1, numSent);
272 
273         Log.i(LOG_TAG, "Running test \"" + safeNextTestName + "\"");
274     }
275 
276     /**
277      * Receive a transfer that has size zero using bulk-transfer.
278      *
279      * @param connection Connection to the USB device
280      * @param in         The in endpoint
281      */
receiveZeroSizedTransfer(@onNull UsbDeviceConnection connection, @NonNull UsbEndpoint in)282     private void receiveZeroSizedTransfer(@NonNull UsbDeviceConnection connection,
283             @NonNull UsbEndpoint in) {
284         byte[] buffer = new byte[1];
285         int numReceived = connection.bulkTransfer(in, buffer, 1, TIMEOUT_MILLIS);
286         assertEquals(0, numReceived);
287     }
288 
289     /**
290      * Send some data and expect it to be echoed back.
291      *
292      * @param connection Connection to the USB device
293      * @param in         The in endpoint
294      * @param out        The out endpoint
295      * @param size       The number of bytes to send
296      */
echoBulkTransfer(@onNull UsbDeviceConnection connection, @NonNull UsbEndpoint in, @NonNull UsbEndpoint out, int size)297     private void echoBulkTransfer(@NonNull UsbDeviceConnection connection,
298             @NonNull UsbEndpoint in, @NonNull UsbEndpoint out, int size) {
299         byte[] sentBuffer = new byte[size];
300         Random r = new Random();
301         r.nextBytes(sentBuffer);
302 
303         int numSent = connection.bulkTransfer(out, sentBuffer, sentBuffer.length, 0);
304         assertEquals(size, numSent);
305 
306         byte[] receivedBuffer = new byte[size];
307         int numReceived = connection.bulkTransfer(in, receivedBuffer, receivedBuffer.length,
308                 TIMEOUT_MILLIS);
309         assertEquals(size, numReceived);
310 
311         assertArrayEquals(sentBuffer, receivedBuffer);
312 
313         if (isZeroTransferExpected(size, in)) {
314             receiveZeroSizedTransfer(connection, in);
315         }
316     }
317 
318     /**
319      * Send some data and expect it to be echoed back (but have an offset in the send buffer).
320      *
321      * @param connection Connection to the USB device
322      * @param in         The in endpoint
323      * @param out        The out endpoint
324      * @param size       The number of bytes to send
325      */
echoBulkTransferOffset(@onNull UsbDeviceConnection connection, @NonNull UsbEndpoint in, @NonNull UsbEndpoint out, int offset, int size)326     private void echoBulkTransferOffset(@NonNull UsbDeviceConnection connection,
327             @NonNull UsbEndpoint in, @NonNull UsbEndpoint out, int offset, int size) {
328         byte[] sentBuffer = new byte[offset + size];
329         Random r = new Random();
330         r.nextBytes(sentBuffer);
331 
332         int numSent = connection.bulkTransfer(out, sentBuffer, offset, size, 0);
333         assertEquals(size, numSent);
334 
335         byte[] receivedBuffer = new byte[offset + size];
336         int numReceived = connection.bulkTransfer(in, receivedBuffer, offset, size, TIMEOUT_MILLIS);
337         assertEquals(size, numReceived);
338 
339         for (int i = 0; i < offset + size; i++) {
340             if (i < offset) {
341                 assertEquals(0, receivedBuffer[i]);
342             } else {
343                 assertEquals(sentBuffer[i], receivedBuffer[i]);
344             }
345         }
346 
347         if (isZeroTransferExpected(size, in)) {
348             receiveZeroSizedTransfer(connection, in);
349         }
350     }
351 
352     /**
353      * Send a transfer that is large.
354      *
355      * @param connection Connection to the USB device
356      * @param in         The in endpoint
357      * @param out        The out endpoint
358      */
echoLargeBulkTransfer(@onNull UsbDeviceConnection connection, @NonNull UsbEndpoint in, @NonNull UsbEndpoint out)359     private void echoLargeBulkTransfer(@NonNull UsbDeviceConnection connection,
360             @NonNull UsbEndpoint in, @NonNull UsbEndpoint out) {
361         int totalSize = LARGE_BUFFER_SIZE;
362         byte[] sentBuffer = new byte[totalSize];
363         Random r = new Random();
364         r.nextBytes(sentBuffer);
365 
366         int numSent = connection.bulkTransfer(out, sentBuffer, sentBuffer.length, 0);
367 
368         // Buffer will be completely transferred
369         assertEquals(LARGE_BUFFER_SIZE, numSent);
370 
371         byte[] receivedBuffer = new byte[totalSize];
372         int numReceived = connection.bulkTransfer(in, receivedBuffer, receivedBuffer.length,
373                 TIMEOUT_MILLIS);
374 
375         // All of the buffer will be echoed back
376         assertEquals(LARGE_BUFFER_SIZE, numReceived);
377 
378         for (int i = 0; i < totalSize; i++) {
379             assertEquals(sentBuffer[i], receivedBuffer[i]);
380         }
381 
382         if (isZeroTransferExpected(LARGE_BUFFER_SIZE, in)) {
383             receiveZeroSizedTransfer(connection, in);
384         }
385     }
386 
387     /**
388      * Receive data but supply an empty buffer. This causes the thread to block until any data is
389      * sent. The zero-sized receive-transfer just returns without data and the next transfer can
390      * actually read the data.
391      *
392      * @param connection Connection to the USB device
393      * @param in         The in endpoint
394      * @param buffer     The buffer to use
395      * @param offset     The offset into the buffer
396      * @param length     The lenght of data to receive
397      */
receiveWithEmptyBuffer(@onNull UsbDeviceConnection connection, @NonNull UsbEndpoint in, @Nullable byte[] buffer, int offset, int length)398     private void receiveWithEmptyBuffer(@NonNull UsbDeviceConnection connection,
399             @NonNull UsbEndpoint in, @Nullable byte[] buffer, int offset, int length) {
400         long startTime = now();
401         int numReceived;
402         if (offset == 0) {
403             numReceived = connection.bulkTransfer(in, buffer, length, 0);
404         } else {
405             numReceived = connection.bulkTransfer(in, buffer, offset, length, 0);
406         }
407         long endTime = now();
408         assertEquals(-1, numReceived);
409 
410         // The transfer should block
411         assertTrue(endTime - startTime > 100);
412 
413         numReceived = connection.bulkTransfer(in, new byte[1], 1, 0);
414         assertEquals(1, numReceived);
415     }
416 
417     /**
418      * Tests {@link UsbDeviceConnection#controlTransfer}.
419      *
420      * <p>Note: We cannot send ctrl data to the device as it thinks it talks to an accessory, hence
421      * the testing is currently limited.</p>
422      *
423      * @param connection The connection to use for testing
424      *
425      * @throws Throwable
426      */
ctrlTransferTests(@onNull UsbDeviceConnection connection)427     private void ctrlTransferTests(@NonNull UsbDeviceConnection connection) throws Throwable {
428         runAndAssertException(() -> connection.controlTransfer(0, 0, 0, 0, null, 1, 0),
429                 IllegalArgumentException.class);
430 
431         runAndAssertException(() -> connection.controlTransfer(0, 0, 0, 0, new byte[1], -1, 0),
432                 IllegalArgumentException.class);
433 
434         runAndAssertException(() -> connection.controlTransfer(0, 0, 0, 0, new byte[1], 2, 0),
435                 IllegalArgumentException.class);
436 
437         runAndAssertException(() -> connection.controlTransfer(0, 0, 0, 0, null, 0, 1, 0),
438                 IllegalArgumentException.class);
439 
440         runAndAssertException(() -> connection.controlTransfer(0, 0, 0, 0, new byte[1], 0, -1, 0),
441                 IllegalArgumentException.class);
442 
443         runAndAssertException(() -> connection.controlTransfer(0, 0, 0, 0, new byte[1], 1, 1, 0),
444                 IllegalArgumentException.class);
445     }
446 
447     /**
448      * Search an {@link UsbInterface} for an {@link UsbEndpoint endpoint} of a certain direction.
449      *
450      * @param iface     The interface to search
451      * @param direction The direction the endpoint is for.
452      *
453      * @return The first endpoint found or {@link null}.
454      */
getEndpoint(@onNull UsbInterface iface, int direction)455     private @NonNull UsbEndpoint getEndpoint(@NonNull UsbInterface iface, int direction) {
456         for (int i = 0; i < iface.getEndpointCount(); i++) {
457             UsbEndpoint ep = iface.getEndpoint(i);
458             if (ep.getDirection() == direction) {
459                 return ep;
460             }
461         }
462 
463         throw new IllegalStateException("Could not find " + direction + " endpoint in "
464                 + iface.getName());
465     }
466 
467     /**
468      * Receive a transfer that has size zero using deprecated usb-request methods.
469      *
470      * @param connection Connection to the USB device
471      * @param in         The in endpoint
472      */
receiveZeroSizeRequestLegacy(@onNull UsbDeviceConnection connection, @NonNull UsbEndpoint in)473     private void receiveZeroSizeRequestLegacy(@NonNull UsbDeviceConnection connection,
474             @NonNull UsbEndpoint in) {
475         UsbRequest receiveZero = new UsbRequest();
476         boolean isInited = receiveZero.initialize(connection, in);
477         assertTrue(isInited);
478         ByteBuffer zeroBuffer = ByteBuffer.allocate(1);
479         receiveZero.queue(zeroBuffer, 1);
480 
481         UsbRequest finished = connection.requestWait();
482         assertEquals(receiveZero, finished);
483         assertEquals(0, zeroBuffer.position());
484     }
485 
486     /**
487      * Send a USB request using the {@link UsbRequest#queue legacy path} and receive it back.
488      *
489      * @param connection      The connection to use
490      * @param in              The endpoint to receive requests from
491      * @param out             The endpoint to send requests to
492      * @param size            The size of the request to send
493      * @param originalSize    The size of the original buffer
494      * @param sliceStart      The start of the final buffer in the original buffer
495      * @param sliceEnd        The end of the final buffer in the original buffer
496      * @param positionInSlice The position parameter in the final buffer
497      * @param limitInSlice    The limited parameter in the final buffer
498      * @param useDirectBuffer If the buffer to be used should be a direct buffer
499      */
echoUsbRequestLegacy(@onNull UsbDeviceConnection connection, @NonNull UsbEndpoint in, @NonNull UsbEndpoint out, int size, int originalSize, int sliceStart, int sliceEnd, int positionInSlice, int limitInSlice, boolean useDirectBuffer)500     private void echoUsbRequestLegacy(@NonNull UsbDeviceConnection connection,
501             @NonNull UsbEndpoint in, @NonNull UsbEndpoint out, int size, int originalSize,
502             int sliceStart, int sliceEnd, int positionInSlice, int limitInSlice,
503             boolean useDirectBuffer) {
504         Random random = new Random();
505 
506         UsbRequest sent = new UsbRequest();
507         boolean isInited = sent.initialize(connection, out);
508         assertTrue(isInited);
509         Object sentClientData = new Object();
510         sent.setClientData(sentClientData);
511 
512         UsbRequest receive = new UsbRequest();
513         isInited = receive.initialize(connection, in);
514         assertTrue(isInited);
515         Object receiveClientData = new Object();
516         receive.setClientData(receiveClientData);
517 
518         ByteBuffer bufferSent;
519         if (useDirectBuffer) {
520             bufferSent = ByteBuffer.allocateDirect(originalSize);
521         } else {
522             bufferSent = ByteBuffer.allocate(originalSize);
523         }
524         for (int i = 0; i < originalSize; i++) {
525             bufferSent.put((byte) random.nextInt());
526         }
527         bufferSent.position(sliceStart);
528         bufferSent.limit(sliceEnd);
529         ByteBuffer bufferSentSliced = bufferSent.slice();
530         bufferSentSliced.position(positionInSlice);
531         bufferSentSliced.limit(limitInSlice);
532 
533         bufferSent.position(0);
534         bufferSent.limit(originalSize);
535 
536         ByteBuffer bufferReceived;
537         if (useDirectBuffer) {
538             bufferReceived = ByteBuffer.allocateDirect(originalSize);
539         } else {
540             bufferReceived = ByteBuffer.allocate(originalSize);
541         }
542         bufferReceived.position(sliceStart);
543         bufferReceived.limit(sliceEnd);
544         ByteBuffer bufferReceivedSliced = bufferReceived.slice();
545         bufferReceivedSliced.position(positionInSlice);
546         bufferReceivedSliced.limit(limitInSlice);
547 
548         bufferReceived.position(0);
549         bufferReceived.limit(originalSize);
550 
551         boolean wasQueued = receive.queue(bufferReceivedSliced, size);
552         assertTrue(wasQueued);
553         wasQueued = sent.queue(bufferSentSliced, size);
554         assertTrue(wasQueued);
555 
556         for (int reqRun = 0; reqRun < 2; reqRun++) {
557             UsbRequest finished;
558 
559             try {
560                 finished = connection.requestWait();
561             } catch (BufferOverflowException e) {
562                 if (size > bufferSentSliced.limit() || size > bufferReceivedSliced.limit()) {
563                     Log.e(LOG_TAG, "Expected failure", e);
564                     continue;
565                 } else {
566                     throw e;
567                 }
568             }
569 
570             // Should we have gotten a failure?
571             if (finished == receive) {
572                 // We should have gotten an exception if size > limit
573                 assertTrue(bufferReceivedSliced.limit() >= size);
574 
575                 assertEquals(size, bufferReceivedSliced.position());
576 
577                 for (int i = 0; i < size; i++) {
578                     if (i < size) {
579                         assertEquals(bufferSent.get(i), bufferReceived.get(i));
580                     } else {
581                         assertEquals(0, bufferReceived.get(i));
582                     }
583                 }
584 
585                 assertSame(receiveClientData, finished.getClientData());
586                 assertSame(in, finished.getEndpoint());
587             } else {
588                 assertEquals(size, bufferSentSliced.position());
589 
590                 // We should have gotten an exception if size > limit
591                 assertTrue(bufferSentSliced.limit() >= size);
592                 assertSame(sent, finished);
593                 assertSame(sentClientData, finished.getClientData());
594                 assertSame(out, finished.getEndpoint());
595             }
596             finished.close();
597         }
598 
599         if (isZeroTransferExpected(size, in)) {
600             receiveZeroSizeRequestLegacy(connection, in);
601         }
602     }
603 
604     /**
605      * Receive a transfer that has size zero using current usb-request methods.
606      *
607      * @param connection Connection to the USB device
608      * @param in         The in endpoint
609      */
receiveZeroSizeRequest(@onNull UsbDeviceConnection connection, @NonNull UsbEndpoint in)610     private void receiveZeroSizeRequest(@NonNull UsbDeviceConnection connection,
611             @NonNull UsbEndpoint in) {
612         UsbRequest receiveZero = new UsbRequest();
613         boolean isInited = receiveZero.initialize(connection, in);
614         assertTrue(isInited);
615         ByteBuffer zeroBuffer = ByteBuffer.allocate(1);
616         receiveZero.queue(zeroBuffer);
617 
618         UsbRequest finished = connection.requestWait();
619         assertEquals(receiveZero, finished);
620         assertEquals(0, zeroBuffer.position());
621     }
622 
623     /**
624      * Send a USB request and receive it back.
625      *
626      * @param connection      The connection to use
627      * @param in              The endpoint to receive requests from
628      * @param out             The endpoint to send requests to
629      * @param originalSize    The size of the original buffer
630      * @param sliceStart      The start of the final buffer in the original buffer
631      * @param sliceEnd        The end of the final buffer in the original buffer
632      * @param positionInSlice The position parameter in the final buffer
633      * @param limitInSlice    The limited parameter in the final buffer
634      * @param useDirectBuffer If the buffer to be used should be a direct buffer
635      */
echoUsbRequest(@onNull UsbDeviceConnection connection, @NonNull UsbEndpoint in, @NonNull UsbEndpoint out, int originalSize, int sliceStart, int sliceEnd, int positionInSlice, int limitInSlice, boolean useDirectBuffer, boolean makeSendBufferReadOnly)636     private void echoUsbRequest(@NonNull UsbDeviceConnection connection, @NonNull UsbEndpoint in,
637             @NonNull UsbEndpoint out, int originalSize, int sliceStart, int sliceEnd,
638             int positionInSlice, int limitInSlice, boolean useDirectBuffer,
639             boolean makeSendBufferReadOnly) {
640         Random random = new Random();
641 
642         UsbRequest sent = new UsbRequest();
643         boolean isInited = sent.initialize(connection, out);
644         assertTrue(isInited);
645         Object sentClientData = new Object();
646         sent.setClientData(sentClientData);
647 
648         UsbRequest receive = new UsbRequest();
649         isInited = receive.initialize(connection, in);
650         assertTrue(isInited);
651         Object receiveClientData = new Object();
652         receive.setClientData(receiveClientData);
653 
654         ByteBuffer bufferSent;
655         if (useDirectBuffer) {
656             bufferSent = ByteBuffer.allocateDirect(originalSize);
657         } else {
658             bufferSent = ByteBuffer.allocate(originalSize);
659         }
660         for (int i = 0; i < originalSize; i++) {
661             bufferSent.put((byte) random.nextInt());
662         }
663         if (makeSendBufferReadOnly) {
664             bufferSent = bufferSent.asReadOnlyBuffer();
665         }
666         bufferSent.position(sliceStart);
667         bufferSent.limit(sliceEnd);
668         ByteBuffer bufferSentSliced = bufferSent.slice();
669         bufferSentSliced.position(positionInSlice);
670         bufferSentSliced.limit(limitInSlice);
671 
672         bufferSent.position(0);
673         bufferSent.limit(originalSize);
674 
675         ByteBuffer bufferReceived;
676         if (useDirectBuffer) {
677             bufferReceived = ByteBuffer.allocateDirect(originalSize);
678         } else {
679             bufferReceived = ByteBuffer.allocate(originalSize);
680         }
681         bufferReceived.position(sliceStart);
682         bufferReceived.limit(sliceEnd);
683         ByteBuffer bufferReceivedSliced = bufferReceived.slice();
684         bufferReceivedSliced.position(positionInSlice);
685         bufferReceivedSliced.limit(limitInSlice);
686 
687         bufferReceived.position(0);
688         bufferReceived.limit(originalSize);
689 
690         boolean wasQueued = receive.queue(bufferReceivedSliced);
691         assertTrue(wasQueued);
692         wasQueued = sent.queue(bufferSentSliced);
693         assertTrue(wasQueued);
694 
695         for (int reqRun = 0; reqRun < 2; reqRun++) {
696             UsbRequest finished = connection.requestWait();
697 
698             if (finished == receive) {
699                 assertEquals(limitInSlice, bufferReceivedSliced.limit());
700                 assertEquals(limitInSlice, bufferReceivedSliced.position());
701 
702                 for (int i = 0; i < originalSize; i++) {
703                     if (i >= sliceStart + positionInSlice && i < sliceStart + limitInSlice) {
704                         assertEquals(bufferSent.get(i), bufferReceived.get(i));
705                     } else {
706                         assertEquals(0, bufferReceived.get(i));
707                     }
708                 }
709 
710                 assertSame(receiveClientData, finished.getClientData());
711                 assertSame(in, finished.getEndpoint());
712             } else {
713                 assertEquals(limitInSlice, bufferSentSliced.limit());
714                 assertEquals(limitInSlice, bufferSentSliced.position());
715 
716                 assertSame(sent, finished);
717                 assertSame(sentClientData, finished.getClientData());
718                 assertSame(out, finished.getEndpoint());
719             }
720             finished.close();
721         }
722 
723         if (isZeroTransferExpected(sliceStart + limitInSlice - (sliceStart + positionInSlice), in)) {
724             receiveZeroSizeRequest(connection, in);
725         }
726     }
727 
728     /**
729      * Send a USB request using the {@link UsbRequest#queue legacy path} and receive it back.
730      *
731      * @param connection      The connection to use
732      * @param in              The endpoint to receive requests from
733      * @param out             The endpoint to send requests to
734      * @param size            The size of the request to send
735      * @param useDirectBuffer If the buffer to be used should be a direct buffer
736      */
echoUsbRequestLegacy(@onNull UsbDeviceConnection connection, @NonNull UsbEndpoint in, @NonNull UsbEndpoint out, int size, boolean useDirectBuffer)737     private void echoUsbRequestLegacy(@NonNull UsbDeviceConnection connection,
738             @NonNull UsbEndpoint in, @NonNull UsbEndpoint out, int size, boolean useDirectBuffer) {
739         echoUsbRequestLegacy(connection, in, out, size, size, 0, size, 0, size, useDirectBuffer);
740     }
741 
742     /**
743      * Send a USB request and receive it back.
744      *
745      * @param connection      The connection to use
746      * @param in              The endpoint to receive requests from
747      * @param out             The endpoint to send requests to
748      * @param size            The size of the request to send
749      * @param useDirectBuffer If the buffer to be used should be a direct buffer
750      */
echoUsbRequest(@onNull UsbDeviceConnection connection, @NonNull UsbEndpoint in, @NonNull UsbEndpoint out, int size, boolean useDirectBuffer)751     private void echoUsbRequest(@NonNull UsbDeviceConnection connection, @NonNull UsbEndpoint in,
752             @NonNull UsbEndpoint out, int size, boolean useDirectBuffer) {
753         echoUsbRequest(connection, in, out, size, 0, size, 0, size, useDirectBuffer, false);
754     }
755 
756     /**
757      * Send a USB request which more than the allowed size and receive it back.
758      *
759      * @param connection      The connection to use
760      * @param in              The endpoint to receive requests from
761      * @param out             The endpoint to send requests to
762      */
echoLargeUsbRequestLegacy(@onNull UsbDeviceConnection connection, @NonNull UsbEndpoint in, @NonNull UsbEndpoint out)763     private void echoLargeUsbRequestLegacy(@NonNull UsbDeviceConnection connection,
764             @NonNull UsbEndpoint in, @NonNull UsbEndpoint out) {
765         Random random = new Random();
766         int totalSize = LARGE_BUFFER_SIZE;
767 
768         UsbRequest sent = new UsbRequest();
769         boolean isInited = sent.initialize(connection, out);
770         assertTrue(isInited);
771 
772         UsbRequest receive = new UsbRequest();
773         isInited = receive.initialize(connection, in);
774         assertTrue(isInited);
775 
776         byte[] sentBytes = new byte[totalSize];
777         random.nextBytes(sentBytes);
778         ByteBuffer bufferSent = ByteBuffer.wrap(sentBytes);
779 
780         byte[] receivedBytes = new byte[totalSize];
781         ByteBuffer bufferReceived = ByteBuffer.wrap(receivedBytes);
782 
783         boolean wasQueued = receive.queue(bufferReceived, totalSize);
784         assertTrue(wasQueued);
785         wasQueued = sent.queue(bufferSent, totalSize);
786         assertTrue(wasQueued);
787 
788         for (int requestNum = 0; requestNum < 2; requestNum++) {
789             UsbRequest finished = connection.requestWait();
790             if (finished == receive) {
791                 // Entire buffer is received
792                 assertEquals(bufferReceived.position(), totalSize);
793                 for (int i = 0; i < totalSize; i++) {
794                     assertEquals(sentBytes[i], receivedBytes[i]);
795                 }
796             } else {
797                 assertSame(sent, finished);
798             }
799             finished.close();
800         }
801 
802         if (isZeroTransferExpected(LARGE_BUFFER_SIZE, in)) {
803             receiveZeroSizedTransfer(connection, in);
804         }
805     }
806 
807     /**
808      * Time out while waiting for USB requests.
809      *
810      * @param connection The connection to use
811      */
timeoutWhileWaitingForUsbRequest(@onNull UsbDeviceConnection connection)812     private void timeoutWhileWaitingForUsbRequest(@NonNull UsbDeviceConnection connection)
813             throws Throwable {
814         runAndAssertException(() -> connection.requestWait(-1), IllegalArgumentException.class);
815 
816         long startTime = now();
817         runAndAssertException(() -> connection.requestWait(100), TimeoutException.class);
818         assertTrue(now() - startTime >= 100);
819         assertTrue(now() - startTime < 400);
820 
821         startTime = now();
822         runAndAssertException(() -> connection.requestWait(0), TimeoutException.class);
823         assertTrue(now() - startTime < 400);
824     }
825 
826     /**
827      * Receive a USB request before a timeout triggers
828      *
829      * @param connection The connection to use
830      * @param in         The endpoint to receive requests from
831      */
832     private void receiveAfterTimeout(@NonNull UsbDeviceConnection connection,
833             @NonNull UsbEndpoint in, long timeout) throws InterruptedException, TimeoutException {
834         UsbRequest reqQueued = new UsbRequest();
835         ByteBuffer buffer = ByteBuffer.allocate(1);
836 
837         reqQueued.initialize(connection, in);
838         reqQueued.queue(buffer);
839 
840         // Let the kernel receive and process the request
841         Thread.sleep(50);
842 
843         long startTime = now();
844         UsbRequest reqFinished = connection.requestWait(timeout);
845         assertTrue(now() - startTime < timeout + 50);
846         assertSame(reqQueued, reqFinished);
847         reqFinished.close();
848     }
849 
850     /**
851      * Send a USB request with size 0 using the {@link UsbRequest#queue legacy path}.
852      *
853      * @param connection      The connection to use
854      * @param out             The endpoint to send requests to
855      * @param useDirectBuffer Send data from a direct buffer
856      */
857     private void sendZeroLengthRequestLegacy(@NonNull UsbDeviceConnection connection,
858             @NonNull UsbEndpoint out, boolean useDirectBuffer) {
859         UsbRequest sent = new UsbRequest();
860         boolean isInited = sent.initialize(connection, out);
861         assertTrue(isInited);
862 
863         ByteBuffer buffer;
864         if (useDirectBuffer) {
865             buffer = ByteBuffer.allocateDirect(0);
866         } else {
867             buffer = ByteBuffer.allocate(0);
868         }
869 
870         boolean isQueued = sent.queue(buffer, 0);
871         assertTrue(isQueued);
872         UsbRequest finished = connection.requestWait();
873         assertSame(finished, sent);
874         finished.close();
875     }
876 
877     /**
878      * Send a USB request with size 0.
879      *
880      * @param connection      The connection to use
881      * @param out             The endpoint to send requests to
882      * @param useDirectBuffer Send data from a direct buffer
883      */
884     private void sendZeroLengthRequest(@NonNull UsbDeviceConnection connection,
885             @NonNull UsbEndpoint out, boolean useDirectBuffer) {
886         UsbRequest sent = new UsbRequest();
887         boolean isInited = sent.initialize(connection, out);
888         assertTrue(isInited);
889 
890         ByteBuffer buffer;
891         if (useDirectBuffer) {
892             buffer = ByteBuffer.allocateDirect(0);
893         } else {
894             buffer = ByteBuffer.allocate(0);
895         }
896 
897         boolean isQueued = sent.queue(buffer);
898         assertTrue(isQueued);
899         UsbRequest finished = connection.requestWait();
900         assertSame(finished, sent);
901         finished.close();
902     }
903 
904     /**
905      * Send a USB request with a null buffer.
906      *
907      * @param connection      The connection to use
908      * @param out             The endpoint to send requests to
909      */
910     private void sendNullRequest(@NonNull UsbDeviceConnection connection,
911             @NonNull UsbEndpoint out) {
912         UsbRequest sent = new UsbRequest();
913         boolean isInited = sent.initialize(connection, out);
914         assertTrue(isInited);
915 
916         boolean isQueued = sent.queue(null);
917         assertTrue(isQueued);
918         UsbRequest finished = connection.requestWait();
919         assertSame(finished, sent);
920         finished.close();
921     }
922 
923     /**
924      * Receive a USB request with size 0.
925      *
926      * @param connection      The connection to use
927      * @param in             The endpoint to recevie requests from
928      */
929     private void receiveZeroLengthRequestLegacy(@NonNull UsbDeviceConnection connection,
930             @NonNull UsbEndpoint in, boolean useDirectBuffer) {
931         UsbRequest zeroReceived = new UsbRequest();
932         boolean isInited = zeroReceived.initialize(connection, in);
933         assertTrue(isInited);
934 
935         UsbRequest oneReceived = new UsbRequest();
936         isInited = oneReceived.initialize(connection, in);
937         assertTrue(isInited);
938 
939         ByteBuffer buffer;
940         if (useDirectBuffer) {
941             buffer = ByteBuffer.allocateDirect(0);
942         } else {
943             buffer = ByteBuffer.allocate(0);
944         }
945 
946         ByteBuffer buffer1;
947         if (useDirectBuffer) {
948             buffer1 = ByteBuffer.allocateDirect(1);
949         } else {
950             buffer1 = ByteBuffer.allocate(1);
951         }
952 
953         boolean isQueued = zeroReceived.queue(buffer);
954         assertTrue(isQueued);
955         isQueued = oneReceived.queue(buffer1);
956         assertTrue(isQueued);
957 
958         // We expect both to be returned after some time
959         ArrayList<UsbRequest> finished = new ArrayList<>(2);
960 
961         // We expect both request to come back after the delay, but then quickly
962         long startTime = now();
963         finished.add(connection.requestWait());
964         long firstReturned = now();
965         finished.add(connection.requestWait());
966         long secondReturned = now();
967 
968         assertTrue(firstReturned - startTime > 100);
969         assertTrue(secondReturned - firstReturned < 100);
970 
971         assertTrue(finished.contains(zeroReceived));
972         assertTrue(finished.contains(oneReceived));
973     }
974 
975     /**
976      * Tests the {@link UsbRequest#queue legacy implementaion} of {@link UsbRequest} and
977      * {@link UsbDeviceConnection#requestWait()}.
978      *
979      * @param connection The connection to use for testing
980      * @param iface      The interface of the android accessory interface of the device
981      * @throws Throwable
982      */
983     private void usbRequestLegacyTests(@NonNull UsbDeviceConnection connection,
984             @NonNull UsbInterface iface) throws Throwable {
985         // Find bulk in and out endpoints
986         assertTrue(iface.getEndpointCount() == 2);
987         final UsbEndpoint in = getEndpoint(iface, UsbConstants.USB_DIR_IN);
988         final UsbEndpoint out = getEndpoint(iface, UsbConstants.USB_DIR_OUT);
989         assertNotNull(in);
990         assertNotNull(out);
991 
992         // Single threaded send and receive
993         nextTest(connection, in, out, "Echo 1 byte");
994         echoUsbRequestLegacy(connection, in, out, 1, true);
995 
996         nextTest(connection, in, out, "Echo 1 byte");
997         echoUsbRequestLegacy(connection, in, out, 1, false);
998 
999         nextTest(connection, in, out, "Echo 16384 bytes");
1000         echoUsbRequestLegacy(connection, in, out, 16384, true);
1001 
1002         nextTest(connection, in, out, "Echo 16384 bytes");
1003         echoUsbRequestLegacy(connection, in, out, 16384, false);
1004 
1005         nextTest(connection, in, out, "Echo large buffer");
1006         echoLargeUsbRequestLegacy(connection, in, out);
1007 
1008         // Send empty requests
1009         sendZeroLengthRequestLegacy(connection, out, true);
1010         sendZeroLengthRequestLegacy(connection, out, false);
1011 
1012         // waitRequest with timeout
1013         timeoutWhileWaitingForUsbRequest(connection);
1014 
1015         nextTest(connection, in, out, "Receive byte after some time");
1016         receiveAfterTimeout(connection, in, 400);
1017 
1018         nextTest(connection, in, out, "Receive byte immediately");
1019         // Make sure the data is received before we queue the request for it
1020         Thread.sleep(50);
1021         receiveAfterTimeout(connection, in, 0);
1022 
1023         /* TODO: Unreliable
1024 
1025         // Zero length means waiting for the next data and then return
1026         nextTest(connection, in, out, "Receive byte after some time");
1027         receiveZeroLengthRequestLegacy(connection, in, true);
1028 
1029         nextTest(connection, in, out, "Receive byte after some time");
1030         receiveZeroLengthRequestLegacy(connection, in, true);
1031 
1032         */
1033 
1034         // UsbRequest.queue ignores position, limit, arrayOffset, and capacity
1035         nextTest(connection, in, out, "Echo 42 bytes");
1036         echoUsbRequestLegacy(connection, in, out, 42, 42, 0, 42, 5, 42, false);
1037 
1038         nextTest(connection, in, out, "Echo 42 bytes");
1039         echoUsbRequestLegacy(connection, in, out, 42, 42, 0, 42, 0, 36, false);
1040 
1041         nextTest(connection, in, out, "Echo 42 bytes");
1042         echoUsbRequestLegacy(connection, in, out, 42, 42, 5, 42, 0, 36, false);
1043 
1044         nextTest(connection, in, out, "Echo 42 bytes");
1045         echoUsbRequestLegacy(connection, in, out, 42, 42, 0, 36, 0, 31, false);
1046 
1047         nextTest(connection, in, out, "Echo 42 bytes");
1048         echoUsbRequestLegacy(connection, in, out, 42, 47, 0, 47, 0, 47, false);
1049 
1050         nextTest(connection, in, out, "Echo 42 bytes");
1051         echoUsbRequestLegacy(connection, in, out, 42, 47, 5, 47, 0, 42, false);
1052 
1053         nextTest(connection, in, out, "Echo 42 bytes");
1054         echoUsbRequestLegacy(connection, in, out, 42, 47, 0, 42, 0, 42, false);
1055 
1056         nextTest(connection, in, out, "Echo 42 bytes");
1057         echoUsbRequestLegacy(connection, in, out, 42, 47, 0, 47, 5, 47, false);
1058 
1059         nextTest(connection, in, out, "Echo 42 bytes");
1060         echoUsbRequestLegacy(connection, in, out, 42, 47, 5, 47, 5, 36, false);
1061 
1062         // Illegal arguments
1063         final UsbRequest req1 = new UsbRequest();
1064         runAndAssertException(() -> req1.initialize(null, in), NullPointerException.class);
1065         runAndAssertException(() -> req1.initialize(connection, null), NullPointerException.class);
1066         boolean isInited = req1.initialize(connection, in);
1067         assertTrue(isInited);
1068         runAndAssertException(() -> req1.queue(null, 0), NullPointerException.class);
1069         runAndAssertException(() -> req1.queue(ByteBuffer.allocate(1).asReadOnlyBuffer(), 1),
1070                 IllegalArgumentException.class);
1071         req1.close();
1072 
1073         // Cannot queue closed request
1074         runAndAssertException(() -> req1.queue(ByteBuffer.allocate(1), 1),
1075                 NullPointerException.class);
1076         runAndAssertException(() -> req1.queue(ByteBuffer.allocateDirect(1), 1),
1077                 NullPointerException.class);
1078     }
1079 
1080     /**
1081      * Repeat c n times
1082      *
1083      * @param c The character to repeat
1084      * @param n The number of times to repeat
1085      *
1086      * @return c repeated n times
1087      */
repeat(char c, int n)1088     public static String repeat(char c, int n) {
1089         final StringBuilder result = new StringBuilder();
1090         for (int i = 0; i < n; i++) {
1091             if (c != ' ' && i % 10 == 0) {
1092                 result.append(i / 10);
1093             } else {
1094                 result.append(c);
1095             }
1096         }
1097         return result.toString();
1098     }
1099 
1100     /**
1101      * Tests {@link UsbRequest} and {@link UsbDeviceConnection#requestWait()}.
1102      *
1103      * @param connection The connection to use for testing
1104      * @param iface      The interface of the android accessory interface of the device
1105      * @throws Throwable
1106      */
usbRequestTests(@onNull UsbDeviceConnection connection, @NonNull UsbInterface iface)1107     private void usbRequestTests(@NonNull UsbDeviceConnection connection,
1108             @NonNull UsbInterface iface) throws Throwable {
1109         // Find bulk in and out endpoints
1110         assertTrue(iface.getEndpointCount() == 2);
1111         final UsbEndpoint in = getEndpoint(iface, UsbConstants.USB_DIR_IN);
1112         final UsbEndpoint out = getEndpoint(iface, UsbConstants.USB_DIR_OUT);
1113         assertNotNull(in);
1114         assertNotNull(out);
1115 
1116         // Single threaded send and receive
1117         nextTest(connection, in, out, "Echo 1 byte");
1118         echoUsbRequest(connection, in, out, 1, true);
1119 
1120         nextTest(connection, in, out, "Echo 1 byte");
1121         echoUsbRequest(connection, in, out, 1, false);
1122 
1123         nextTest(connection, in, out, "Echo 16384 bytes");
1124         echoUsbRequest(connection, in, out, 16384, true);
1125 
1126         nextTest(connection, in, out, "Echo 16384 bytes");
1127         echoUsbRequest(connection, in, out, 16384, false);
1128 
1129         // Send empty requests
1130         sendZeroLengthRequest(connection, out, true);
1131         sendZeroLengthRequest(connection, out, false);
1132         sendNullRequest(connection, out);
1133 
1134         /* TODO: Unreliable
1135 
1136         // Zero length means waiting for the next data and then return
1137         nextTest(connection, in, out, "Receive byte after some time");
1138         receiveZeroLengthRequest(connection, in, true);
1139 
1140         nextTest(connection, in, out, "Receive byte after some time");
1141         receiveZeroLengthRequest(connection, in, true);
1142 
1143         */
1144 
1145         for (int startOfSlice : new int[]{0, 1}) {
1146             for (int endOffsetOfSlice : new int[]{0, 2}) {
1147                 for (int positionInSlice : new int[]{0, 5}) {
1148                     for (int limitOffsetInSlice : new int[]{0, 11}) {
1149                         for (boolean useDirectBuffer : new boolean[]{true, false}) {
1150                             for (boolean makeSendBufferReadOnly : new boolean[]{true, false}) {
1151                                 int sliceSize = 42 + positionInSlice + limitOffsetInSlice;
1152                                 int originalSize = sliceSize + startOfSlice + endOffsetOfSlice;
1153 
1154                                 nextTest(connection, in, out, "Echo 42 bytes");
1155 
1156                                 // Log buffer, slice, and data offsets
1157                                 Log.i(LOG_TAG,
1158                                         "buffer" + (makeSendBufferReadOnly ? "(ro): [" : ":     [")
1159                                                 + repeat('.', originalSize) + "]");
1160                                 Log.i(LOG_TAG,
1161                                         "slice:     " + repeat(' ', startOfSlice) + " [" + repeat(
1162                                                 '.', sliceSize) + "]");
1163                                 Log.i(LOG_TAG,
1164                                         "data:      " + repeat(' ', startOfSlice + positionInSlice)
1165                                                 + " [" + repeat('.', 42) + "]");
1166 
1167                                 echoUsbRequest(connection, in, out, originalSize, startOfSlice,
1168                                         originalSize - endOffsetOfSlice, positionInSlice,
1169                                         sliceSize - limitOffsetInSlice, useDirectBuffer,
1170                                         makeSendBufferReadOnly);
1171                             }
1172                         }
1173                     }
1174                 }
1175             }
1176         }
1177 
1178         // Illegal arguments
1179         final UsbRequest req1 = new UsbRequest();
1180         runAndAssertException(() -> req1.initialize(null, in), NullPointerException.class);
1181         runAndAssertException(() -> req1.initialize(connection, null), NullPointerException.class);
1182         boolean isInited = req1.initialize(connection, in);
1183         assertTrue(isInited);
1184         runAndAssertException(() -> req1.queue(ByteBuffer.allocate(16384 + 1).asReadOnlyBuffer()),
1185                 IllegalArgumentException.class);
1186         runAndAssertException(() -> req1.queue(ByteBuffer.allocate(1).asReadOnlyBuffer()),
1187                 IllegalArgumentException.class);
1188         req1.close();
1189 
1190         // Cannot queue closed request
1191         runAndAssertException(() -> req1.queue(ByteBuffer.allocate(1)),
1192                 IllegalStateException.class);
1193         runAndAssertException(() -> req1.queue(ByteBuffer.allocateDirect(1)),
1194                 IllegalStateException.class);
1195 
1196         // Initialize
1197         UsbRequest req2 = new UsbRequest();
1198         isInited = req2.initialize(connection, in);
1199         assertTrue(isInited);
1200         isInited = req2.initialize(connection, out);
1201         assertTrue(isInited);
1202         req2.close();
1203 
1204         // Close
1205         req2 = new UsbRequest();
1206         req2.close();
1207 
1208         req2.initialize(connection, in);
1209         req2.close();
1210         req2.close();
1211     }
1212 
1213     /** State of a {@link UsbRequest} in flight */
1214     private static class RequestState {
1215         final ByteBuffer buffer;
1216         final Object clientData;
1217 
RequestState(ByteBuffer buffer, Object clientData)1218         private RequestState(ByteBuffer buffer, Object clientData) {
1219             this.buffer = buffer;
1220             this.clientData = clientData;
1221         }
1222     }
1223 
1224     /** Recycles elements that might be expensive to create */
1225     private abstract class Recycler<T> {
1226         private final Random mRandom;
1227         private final LinkedList<T> mData;
1228 
Recycler()1229         protected Recycler() {
1230             mData = new LinkedList<>();
1231             mRandom = new Random();
1232         }
1233 
1234         /**
1235          * Add a new element to be recycled.
1236          *
1237          * @param newElement The element that is not used anymore and can be used by someone else.
1238          */
recycle(@onNull T newElement)1239         private void recycle(@NonNull T newElement) {
1240             synchronized (mData) {
1241                 if (mRandom.nextBoolean()) {
1242                     mData.addLast(newElement);
1243                 } else {
1244                     mData.addFirst(newElement);
1245                 }
1246             }
1247         }
1248 
1249         /**
1250          * Get a recycled element or create a new one if needed.
1251          *
1252          * @return An element that can be used (maybe recycled)
1253          */
get()1254         private @NonNull T get() {
1255             T recycledElement;
1256 
1257             try {
1258                 synchronized (mData) {
1259                     recycledElement = mData.pop();
1260                 }
1261             } catch (NoSuchElementException ignored) {
1262                 recycledElement = create();
1263             }
1264 
1265             reset(recycledElement);
1266 
1267             return recycledElement;
1268         }
1269 
1270         /** Reset internal state of {@code recycledElement} */
reset(@onNull T recycledElement)1271         protected abstract void reset(@NonNull T recycledElement);
1272 
1273         /** Create a new element */
create()1274         protected abstract @NonNull T create();
1275 
1276         /** Get all elements that are currently recycled and waiting to be used again */
getAll()1277         public @NonNull LinkedList<T> getAll() {
1278             return mData;
1279         }
1280     }
1281 
1282     /**
1283      * Common code between {@link QueuerThread} and {@link ReceiverThread}.
1284      */
1285     private class TestThread extends Thread {
1286         /** State copied from the main thread (see runTest()) */
1287         protected final UsbDeviceConnection mConnection;
1288         protected final Recycler<UsbRequest> mInRequestRecycler;
1289         protected final Recycler<UsbRequest> mOutRequestRecycler;
1290         protected final Recycler<ByteBuffer> mBufferRecycler;
1291         protected final HashMap<UsbRequest, RequestState> mRequestsInFlight;
1292         protected final HashMap<Integer, Integer> mData;
1293         protected final ArrayList<Throwable> mErrors;
1294 
1295         protected volatile boolean mShouldStop;
1296 
TestThread(@onNull UsbDeviceConnection connection, @NonNull Recycler<UsbRequest> inRequestRecycler, @NonNull Recycler<UsbRequest> outRequestRecycler, @NonNull Recycler<ByteBuffer> bufferRecycler, @NonNull HashMap<UsbRequest, RequestState> requestsInFlight, @NonNull HashMap<Integer, Integer> data, @NonNull ArrayList<Throwable> errors)1297         TestThread(@NonNull UsbDeviceConnection connection,
1298                 @NonNull Recycler<UsbRequest> inRequestRecycler,
1299                 @NonNull Recycler<UsbRequest> outRequestRecycler,
1300                 @NonNull Recycler<ByteBuffer> bufferRecycler,
1301                 @NonNull HashMap<UsbRequest, RequestState> requestsInFlight,
1302                 @NonNull HashMap<Integer, Integer> data,
1303                 @NonNull ArrayList<Throwable> errors) {
1304             super();
1305 
1306             mShouldStop = false;
1307             mConnection = connection;
1308             mBufferRecycler = bufferRecycler;
1309             mInRequestRecycler = inRequestRecycler;
1310             mOutRequestRecycler = outRequestRecycler;
1311             mRequestsInFlight = requestsInFlight;
1312             mData = data;
1313             mErrors = errors;
1314         }
1315 
1316         /**
1317          * Stop thread
1318          */
abort()1319         void abort() {
1320             mShouldStop = true;
1321             interrupt();
1322         }
1323     }
1324 
1325     /**
1326      * A thread that queues matching write and read {@link UsbRequest requests}. We expect the
1327      * writes to be echoed back and return in unchanged in the read requests.
1328      * <p> This thread just issues the requests and does not care about them anymore after the
1329      * system took them. The {@link ReceiverThread} handles the result of both write and read
1330      * requests.</p>
1331      */
1332     private class QueuerThread extends TestThread {
1333         private static final int MAX_IN_FLIGHT = 64;
1334         private static final long RUN_TIME = 10 * 1000;
1335 
1336         private final AtomicInteger mCounter;
1337 
1338         /**
1339          * Create a new thread that queues matching write and read UsbRequests.
1340          *
1341          * @param connection Connection to communicate with
1342          * @param inRequestRecycler Pool of in-requests that can be reused
1343          * @param outRequestRecycler Pool of out-requests that can be reused
1344          * @param bufferRecycler Pool of byte buffers that can be reused
1345          * @param requestsInFlight State of the requests currently in flight
1346          * @param data Mapping counter -> data
1347          * @param counter An atomic counter
1348          * @param errors Pool of throwables created by threads like this
1349          */
QueuerThread(@onNull UsbDeviceConnection connection, @NonNull Recycler<UsbRequest> inRequestRecycler, @NonNull Recycler<UsbRequest> outRequestRecycler, @NonNull Recycler<ByteBuffer> bufferRecycler, @NonNull HashMap<UsbRequest, RequestState> requestsInFlight, @NonNull HashMap<Integer, Integer> data, @NonNull AtomicInteger counter, @NonNull ArrayList<Throwable> errors)1350         QueuerThread(@NonNull UsbDeviceConnection connection,
1351                 @NonNull Recycler<UsbRequest> inRequestRecycler,
1352                 @NonNull Recycler<UsbRequest> outRequestRecycler,
1353                 @NonNull Recycler<ByteBuffer> bufferRecycler,
1354                 @NonNull HashMap<UsbRequest, RequestState> requestsInFlight,
1355                 @NonNull HashMap<Integer, Integer> data,
1356                 @NonNull AtomicInteger counter,
1357                 @NonNull ArrayList<Throwable> errors) {
1358             super(connection, inRequestRecycler, outRequestRecycler, bufferRecycler,
1359                     requestsInFlight, data, errors);
1360 
1361             mCounter = counter;
1362         }
1363 
1364         @Override
run()1365         public void run() {
1366             Random random = new Random();
1367 
1368             long endTime = now() + RUN_TIME;
1369 
1370             while (now() < endTime && !mShouldStop) {
1371                 try {
1372                     int counter = mCounter.getAndIncrement();
1373 
1374                     if (counter % 1024 == 0) {
1375                         Log.i(LOG_TAG, "Counter is " + counter);
1376                     }
1377 
1378                     // Write [1:counter:data]
1379                     UsbRequest writeRequest = mOutRequestRecycler.get();
1380                     ByteBuffer writeBuffer = mBufferRecycler.get();
1381                     int data = random.nextInt();
1382                     writeBuffer.put((byte)1).putInt(counter).putInt(data);
1383                     writeBuffer.flip();
1384 
1385                     // Send read that will receive the data back from the write as the other side
1386                     // will echo all requests.
1387                     UsbRequest readRequest = mInRequestRecycler.get();
1388                     ByteBuffer readBuffer = mBufferRecycler.get();
1389 
1390                     // Register requests
1391                     synchronized (mRequestsInFlight) {
1392                         // Wait until previous requests were processed
1393                         while (mRequestsInFlight.size() > MAX_IN_FLIGHT) {
1394                             try {
1395                                 mRequestsInFlight.wait();
1396                             } catch (InterruptedException e) {
1397                                 break;
1398                             }
1399                         }
1400 
1401                         if (mShouldStop) {
1402                             break;
1403                         } else {
1404                             mRequestsInFlight.put(writeRequest, new RequestState(writeBuffer,
1405                                     writeRequest.getClientData()));
1406                             mRequestsInFlight.put(readRequest, new RequestState(readBuffer,
1407                                     readRequest.getClientData()));
1408                             mRequestsInFlight.notifyAll();
1409                         }
1410                     }
1411 
1412                     // Store which data was written for the counter
1413                     synchronized (mData) {
1414                         mData.put(counter, data);
1415                     }
1416 
1417                     // Send both requests to the system. Once they finish the ReceiverThread will
1418                     // be notified
1419                     boolean isQueued = writeRequest.queue(writeBuffer);
1420                     assertTrue(isQueued);
1421 
1422                     isQueued = readRequest.queue(readBuffer, 9);
1423                     assertTrue(isQueued);
1424                 } catch (Throwable t) {
1425                     synchronized (mErrors) {
1426                         mErrors.add(t);
1427                         mErrors.notify();
1428                     }
1429                     break;
1430                 }
1431             }
1432         }
1433     }
1434 
1435     /**
1436      * A thread that receives processed UsbRequests and compares the expected result. The requests
1437      * can be both read and write requests. The requests were created and given to the system by
1438      * the {@link QueuerThread}.
1439      */
1440     private class ReceiverThread extends TestThread {
1441         private final UsbEndpoint mOut;
1442 
1443         /**
1444          * Create a thread that receives processed UsbRequests and compares the expected result.
1445          *
1446          * @param connection Connection to communicate with
1447          * @param out Endpoint to queue write requests on
1448          * @param inRequestRecycler Pool of in-requests that can be reused
1449          * @param outRequestRecycler Pool of out-requests that can be reused
1450          * @param bufferRecycler Pool of byte buffers that can be reused
1451          * @param requestsInFlight State of the requests currently in flight
1452          * @param data Mapping counter -> data
1453          * @param errors Pool of throwables created by threads like this
1454          */
ReceiverThread(@onNull UsbDeviceConnection connection, @NonNull UsbEndpoint out, @NonNull Recycler<UsbRequest> inRequestRecycler, @NonNull Recycler<UsbRequest> outRequestRecycler, @NonNull Recycler<ByteBuffer> bufferRecycler, @NonNull HashMap<UsbRequest, RequestState> requestsInFlight, @NonNull HashMap<Integer, Integer> data, @NonNull ArrayList<Throwable> errors)1455         ReceiverThread(@NonNull UsbDeviceConnection connection, @NonNull UsbEndpoint out,
1456                 @NonNull Recycler<UsbRequest> inRequestRecycler,
1457                 @NonNull Recycler<UsbRequest> outRequestRecycler,
1458                 @NonNull Recycler<ByteBuffer> bufferRecycler,
1459                 @NonNull HashMap<UsbRequest, RequestState> requestsInFlight,
1460                 @NonNull HashMap<Integer, Integer> data, @NonNull ArrayList<Throwable> errors) {
1461             super(connection, inRequestRecycler, outRequestRecycler, bufferRecycler,
1462                     requestsInFlight, data, errors);
1463 
1464             mOut = out;
1465         }
1466 
1467         @Override
run()1468         public void run() {
1469             while (!mShouldStop) {
1470                 try {
1471                     // Wait until a request is queued as mConnection.requestWait() cannot be
1472                     // interrupted.
1473                     synchronized (mRequestsInFlight) {
1474                         while (mRequestsInFlight.isEmpty()) {
1475                             try {
1476                                 mRequestsInFlight.wait();
1477                             } catch (InterruptedException e) {
1478                                 break;
1479                             }
1480                         }
1481 
1482                         if (mShouldStop) {
1483                             break;
1484                         }
1485                     }
1486 
1487                     // Receive request
1488                     UsbRequest request = mConnection.requestWait();
1489                     assertNotNull(request);
1490 
1491                     // Find the state the request should have
1492                     RequestState state;
1493                     synchronized (mRequestsInFlight) {
1494                         state = mRequestsInFlight.remove(request);
1495                         mRequestsInFlight.notifyAll();
1496                     }
1497 
1498                     // Compare client data
1499                     assertSame(state.clientData, request.getClientData());
1500 
1501                     // There is nothing more to check about write requests, but for read requests
1502                     // (the ones going to an out endpoint) we know that it just an echoed back write
1503                     // request.
1504                     if (!request.getEndpoint().equals(mOut)) {
1505                         state.buffer.flip();
1506 
1507                         // Read request buffer, check that data is correct
1508                         byte alive = state.buffer.get();
1509                         int counter = state.buffer.getInt();
1510                         int receivedData = state.buffer.getInt();
1511 
1512                         // We stored which data-combinations were written
1513                         int expectedData;
1514                         synchronized(mData) {
1515                             expectedData = mData.remove(counter);
1516                         }
1517 
1518                         // Make sure read request matches a write request we sent before
1519                         assertEquals(1, alive);
1520                         assertEquals(expectedData, receivedData);
1521                     }
1522 
1523                     // Recycle buffers and requests so they can be reused later.
1524                     mBufferRecycler.recycle(state.buffer);
1525 
1526                     if (request.getEndpoint().equals(mOut)) {
1527                         mOutRequestRecycler.recycle(request);
1528                     } else {
1529                         mInRequestRecycler.recycle(request);
1530                     }
1531                 } catch (Throwable t) {
1532                     synchronized (mErrors) {
1533                         mErrors.add(t);
1534                         mErrors.notify();
1535                     }
1536                     break;
1537                 }
1538             }
1539         }
1540     }
1541 
1542     /**
1543      * Run reconnecttest.
1544      *
1545      * @param device The device to run the test against. This device is running
1546      *               com.android.cts.verifierusbcompanion.DeviceTestCompanion
1547      *
1548      * @throws Throwable
1549      */
reconnectTest(@onNull UsbDevice device)1550     private void reconnectTest(@NonNull UsbDevice device) throws Throwable {
1551         UsbDeviceConnection connection = mUsbManager.openDevice(device);
1552         assertNotNull(connection);
1553 
1554         assertFalse(connection.getFileDescriptor() == -1);
1555         assertNotNull(connection.getRawDescriptors());
1556         assertFalse(connection.getRawDescriptors().length == 0);
1557         assertEquals(device.getSerialNumber(), connection.getSerial());
1558         runAndAssertException(() -> connection.setConfiguration(null), NullPointerException.class);
1559         connection.close();
1560     }
1561 
syncReconnectDevice(@onNull UsbDevice device)1562     private void syncReconnectDevice(@NonNull UsbDevice device) {
1563         this.mDevice = device;
1564     }
1565 
getReconnectDevice()1566     private UsbDevice getReconnectDevice() {
1567         return mDevice;
1568     }
1569 
1570 
1571     /**
1572      * <p> This attachedtask the requests and does not care about them anymore after the
1573      * system took them. The {@link attachedTask} handles the test after the main test done.
1574      * It should start after device reconnect success.</p>
1575      */
attachedTask()1576     private ArrayList<Throwable> attachedTask() {
1577         final ArrayList<Throwable> mErrors = new ArrayList<>();
1578 
1579         // Reconnect and give permission time should under 9 second
1580         long mAttachedConfirmTime = 9 * 1000;
1581 
1582         CompletableFuture<Void> mAttachedThreadFinished = new CompletableFuture<>();
1583 
1584         IntentFilter filter = new IntentFilter();
1585         filter.addAction(ACTION_USB_PERMISSION);
1586         filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
1587 
1588         mUsbDeviceAttachedReceiver = new BroadcastReceiver() {
1589             @Override
1590             public void onReceive(Context context, Intent intent) {
1591                 synchronized (UsbDeviceTestActivity.this) {
1592                     UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
1593 
1594                     switch (intent.getAction()) {
1595                         case UsbManager.ACTION_USB_DEVICE_ATTACHED:
1596                             if (!AoapInterface.isDeviceInAoapMode(device)) {
1597                                 mStatus.setText(R.string.usb_device_test_step2);
1598                             }
1599 
1600                             mUsbManager.requestPermission(device,
1601                                     PendingIntent.getBroadcast(UsbDeviceTestActivity.this, 0,
1602                                             new Intent(ACTION_USB_PERMISSION)
1603                                                     .setPackage(UsbDeviceTestActivity.this
1604                                                             .getPackageName()),
1605                                             PendingIntent.FLAG_MUTABLE));
1606                             break;
1607                     }
1608                 }
1609             }
1610         };
1611 
1612         mUsbDevicePermissionReceiver = new BroadcastReceiver() {
1613             @Override
1614             public void onReceive(Context context, Intent intent) {
1615                 synchronized (UsbDeviceTestActivity.this) {
1616                     UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
1617                     syncReconnectDevice(device);
1618 
1619                     switch (intent.getAction()) {
1620                         case ACTION_USB_PERMISSION:
1621                             boolean granted = intent.getBooleanExtra(
1622                                     UsbManager.EXTRA_PERMISSION_GRANTED, false);
1623                             if (granted) {
1624                                 if (!AoapInterface.isDeviceInAoapMode(device)) {
1625                                     mStatus.setText(R.string.usb_device_test_step3);
1626 
1627                                     UsbDeviceConnection connection =
1628                                             mUsbManager.openDevice(device);
1629                                     try {
1630                                         makeThisDeviceAnAccessory(connection);
1631                                     } finally {
1632                                         connection.close();
1633                                     }
1634                                 } else {
1635                                     mStatus.setText(R.string.usb_device_test_step4);
1636                                     mProgress.setIndeterminate(true);
1637                                     mProgress.setVisibility(View.VISIBLE);
1638 
1639                                     UsbDeviceConnection connection =
1640                                             mUsbManager.openDevice(device);
1641                                     assertNotNull(connection);
1642 
1643                                     try {
1644                                         setConfigurationTests(device);
1645                                     } catch (Throwable e) {
1646                                         synchronized (mErrors) {
1647                                             mErrors.add(e);
1648                                         }
1649                                     }
1650                                     try {
1651                                         reconnectTest(device);
1652                                     } catch (Throwable e) {
1653                                         synchronized (mErrors) {
1654                                             mErrors.add(e);
1655                                         }
1656                                     }
1657 
1658                                     mAttachedThreadFinished.complete(null);
1659                                 }
1660                             } else {
1661                                 fail("Permission to connect to " + device.getProductName()
1662                                         + " not granted", null);
1663                             }
1664                             break;
1665                     }
1666                 }
1667             }
1668         };
1669 
1670         registerReceiver(mUsbDeviceAttachedReceiver, filter, Context.RECEIVER_EXPORTED);
1671         registerReceiver(mUsbDevicePermissionReceiver, filter, Context.RECEIVER_EXPORTED);
1672 
1673         try {
1674             mAttachedThreadFinished.get(mAttachedConfirmTime, TimeUnit.MILLISECONDS);
1675         } catch (Throwable e) {
1676             synchronized (mErrors) {
1677                 mErrors.add(e);
1678             }
1679         }
1680 
1681         unregisterReceiver(mUsbDeviceAttachedReceiver);
1682         mUsbDeviceAttachedReceiver = null;
1683 
1684         unregisterReceiver(mUsbDevicePermissionReceiver);
1685         mUsbDevicePermissionReceiver = null;
1686 
1687         return mErrors;
1688     }
1689 
1690     /**
1691      * Tests parallel issuance and receiving of {@link UsbRequest usb requests}.
1692      *
1693      * @param connection The connection to use for testing
1694      * @param iface      The interface of the android accessory interface of the device
1695      */
parallelUsbRequestsTests(@onNull UsbDeviceConnection connection, @NonNull UsbInterface iface)1696     private void parallelUsbRequestsTests(@NonNull UsbDeviceConnection connection,
1697             @NonNull UsbInterface iface) {
1698         // Find bulk in and out endpoints
1699         assertTrue(iface.getEndpointCount() == 2);
1700         final UsbEndpoint in = getEndpoint(iface, UsbConstants.USB_DIR_IN);
1701         final UsbEndpoint out = getEndpoint(iface, UsbConstants.USB_DIR_OUT);
1702         assertNotNull(in);
1703         assertNotNull(out);
1704 
1705         // Recycler for requests for the in-endpoint
1706         Recycler<UsbRequest> inRequestRecycler = new Recycler<UsbRequest>() {
1707             @Override
1708             protected void reset(@NonNull UsbRequest recycledElement) {
1709                 recycledElement.setClientData(new Object());
1710             }
1711 
1712             @Override
1713             protected @NonNull UsbRequest create() {
1714                 UsbRequest request = new UsbRequest();
1715                 request.initialize(connection, in);
1716 
1717                 return request;
1718             }
1719         };
1720 
1721         // Recycler for requests for the in-endpoint
1722         Recycler<UsbRequest> outRequestRecycler = new Recycler<UsbRequest>() {
1723             @Override
1724             protected void reset(@NonNull UsbRequest recycledElement) {
1725                 recycledElement.setClientData(new Object());
1726             }
1727 
1728             @Override
1729             protected @NonNull UsbRequest create() {
1730                 UsbRequest request = new UsbRequest();
1731                 request.initialize(connection, out);
1732 
1733                 return request;
1734             }
1735         };
1736 
1737         // Recycler for requests for read and write buffers
1738         Recycler<ByteBuffer> bufferRecycler = new Recycler<ByteBuffer>() {
1739             @Override
1740             protected void reset(@NonNull ByteBuffer recycledElement) {
1741                 recycledElement.rewind();
1742             }
1743 
1744             @Override
1745             protected @NonNull ByteBuffer create() {
1746                 return ByteBuffer.allocateDirect(9);
1747             }
1748         };
1749 
1750         HashMap<UsbRequest, RequestState> requestsInFlight = new HashMap<>();
1751 
1752         // Data in the requests
1753         HashMap<Integer, Integer> data = new HashMap<>();
1754         AtomicInteger counter = new AtomicInteger(0);
1755 
1756         // Errors created in the threads
1757         ArrayList<Throwable> errors = new ArrayList<>();
1758 
1759         // Create two threads that queue read and write requests
1760         QueuerThread queuer1 = new QueuerThread(connection, inRequestRecycler,
1761                 outRequestRecycler, bufferRecycler, requestsInFlight, data, counter, errors);
1762         QueuerThread queuer2 = new QueuerThread(connection, inRequestRecycler,
1763                 outRequestRecycler, bufferRecycler, requestsInFlight, data, counter, errors);
1764 
1765         // Create a thread that receives the requests after they are processed.
1766         ReceiverThread receiver = new ReceiverThread(connection, out, inRequestRecycler,
1767                 outRequestRecycler, bufferRecycler, requestsInFlight, data, errors);
1768 
1769         nextTest(connection, in, out, "Echo until stop signal");
1770 
1771         queuer1.start();
1772         queuer2.start();
1773         receiver.start();
1774 
1775         Log.i(LOG_TAG, "Waiting for queuers to stop");
1776 
1777         try {
1778             queuer1.join();
1779             queuer2.join();
1780         } catch (InterruptedException e) {
1781             synchronized(errors) {
1782                 errors.add(e);
1783             }
1784         }
1785 
1786         if (errors.isEmpty()) {
1787             Log.i(LOG_TAG, "Wait for all requests to finish");
1788             synchronized (requestsInFlight) {
1789                 while (!requestsInFlight.isEmpty()) {
1790                     try {
1791                         requestsInFlight.wait();
1792                     } catch (InterruptedException e) {
1793                         synchronized(errors) {
1794                             errors.add(e);
1795                         }
1796                         break;
1797                     }
1798                 }
1799             }
1800 
1801             receiver.abort();
1802 
1803             try {
1804                 receiver.join();
1805             } catch (InterruptedException e) {
1806                 synchronized(errors) {
1807                     errors.add(e);
1808                 }
1809             }
1810 
1811             // Close all requests that are currently recycled
1812             inRequestRecycler.getAll().forEach(UsbRequest::close);
1813             outRequestRecycler.getAll().forEach(UsbRequest::close);
1814         } else {
1815             receiver.abort();
1816         }
1817 
1818         for (Throwable t : errors) {
1819             Log.e(LOG_TAG, "Error during test", t);
1820         }
1821 
1822         byte[] stopBytes = new byte[9];
1823         connection.bulkTransfer(out, stopBytes, 9, 0);
1824 
1825         // If we had any error make the test fail
1826         assertEquals(0, errors.size());
1827     }
1828 
1829     /**
1830      * Tests {@link UsbDeviceConnection#bulkTransfer}.
1831      *
1832      * @param connection The connection to use for testing
1833      * @param iface      The interface of the android accessory interface of the device
1834      * @throws Throwable
1835      */
bulkTransferTests(@onNull UsbDeviceConnection connection, @NonNull UsbInterface iface)1836     private void bulkTransferTests(@NonNull UsbDeviceConnection connection,
1837             @NonNull UsbInterface iface) throws Throwable {
1838         // Find bulk in and out endpoints
1839         assertTrue(iface.getEndpointCount() == 2);
1840         final UsbEndpoint in = getEndpoint(iface, UsbConstants.USB_DIR_IN);
1841         final UsbEndpoint out = getEndpoint(iface, UsbConstants.USB_DIR_OUT);
1842         assertNotNull(in);
1843         assertNotNull(out);
1844 
1845         // Transmission tests
1846         nextTest(connection, in, out, "Echo 1 byte");
1847         echoBulkTransfer(connection, in, out, 1);
1848 
1849         nextTest(connection, in, out, "Echo 42 bytes");
1850         echoBulkTransferOffset(connection, in, out, 23, 42);
1851 
1852         nextTest(connection, in, out, "Echo 16384 bytes");
1853         echoBulkTransfer(connection, in, out, 16384);
1854 
1855         nextTest(connection, in, out, "Echo large buffer");
1856         echoLargeBulkTransfer(connection, in, out);
1857 
1858         // Illegal arguments
1859         runAndAssertException(() -> connection.bulkTransfer(out, new byte[1], 2, 0),
1860                 IllegalArgumentException.class);
1861         runAndAssertException(() -> connection.bulkTransfer(in, new byte[1], 2, 0),
1862                 IllegalArgumentException.class);
1863         runAndAssertException(() -> connection.bulkTransfer(out, new byte[2], 1, 2, 0),
1864                 IllegalArgumentException.class);
1865         runAndAssertException(() -> connection.bulkTransfer(in, new byte[2], 1, 2, 0),
1866                 IllegalArgumentException.class);
1867         runAndAssertException(() -> connection.bulkTransfer(out, new byte[1], -1, 0),
1868                 IllegalArgumentException.class);
1869         runAndAssertException(() -> connection.bulkTransfer(in, new byte[1], -1, 0),
1870                 IllegalArgumentException.class);
1871         runAndAssertException(() -> connection.bulkTransfer(out, new byte[1], 1, -1, 0),
1872                 IllegalArgumentException.class);
1873         runAndAssertException(() -> connection.bulkTransfer(in, new byte[1], 1, -1, 0),
1874                 IllegalArgumentException.class);
1875         runAndAssertException(() -> connection.bulkTransfer(out, new byte[1], -1, -1, 0),
1876                 IllegalArgumentException.class);
1877         runAndAssertException(() -> connection.bulkTransfer(in, new byte[1], -1, -1, 0),
1878                 IllegalArgumentException.class);
1879         runAndAssertException(() -> connection.bulkTransfer(null, new byte[1], 1, 0),
1880                 NullPointerException.class);
1881 
1882         // Transmissions that do nothing
1883         int numSent = connection.bulkTransfer(out, null, 0, 0);
1884         assertEquals(0, numSent);
1885 
1886         numSent = connection.bulkTransfer(out, null, 0, 0, 0);
1887         assertEquals(0, numSent);
1888 
1889         numSent = connection.bulkTransfer(out, new byte[0], 0, 0);
1890         assertEquals(0, numSent);
1891 
1892         numSent = connection.bulkTransfer(out, new byte[0], 0, 0, 0);
1893         assertEquals(0, numSent);
1894 
1895         numSent = connection.bulkTransfer(out, new byte[2], 2, 0, 0);
1896         assertEquals(0, numSent);
1897 
1898         /* TODO: These tests are flaky as they appear to be affected by previous tests
1899 
1900         // Transmissions that do not transfer data:
1901         // - first transfer blocks until data is received, but does not return the data.
1902         // - The data is read in the second transfer
1903         nextTest(connection, in, out, "Receive byte after some time");
1904         receiveWithEmptyBuffer(connection, in, null, 0, 0);
1905 
1906         nextTest(connection, in, out, "Receive byte after some time");
1907         receiveWithEmptyBuffer(connection, in, new byte[0], 0, 0);
1908 
1909         nextTest(connection, in, out, "Receive byte after some time");
1910         receiveWithEmptyBuffer(connection, in, new byte[2], 2, 0);
1911 
1912         */
1913 
1914         // Timeouts
1915         int numReceived = connection.bulkTransfer(in, new byte[1], 1, 100);
1916         assertEquals(-1, numReceived);
1917 
1918         nextTest(connection, in, out, "Receive byte after some time");
1919         numReceived = connection.bulkTransfer(in, new byte[1], 1, 10000);
1920         assertEquals(1, numReceived);
1921 
1922         nextTest(connection, in, out, "Receive byte after some time");
1923         numReceived = connection.bulkTransfer(in, new byte[1], 1, 0);
1924         assertEquals(1, numReceived);
1925 
1926         nextTest(connection, in, out, "Receive byte after some time");
1927         numReceived = connection.bulkTransfer(in, new byte[1], 1, -1);
1928         assertEquals(1, numReceived);
1929 
1930         numReceived = connection.bulkTransfer(in, new byte[2], 1, 1, 100);
1931         assertEquals(-1, numReceived);
1932 
1933         nextTest(connection, in, out, "Receive byte after some time");
1934         numReceived = connection.bulkTransfer(in, new byte[2], 1, 1, 0);
1935         assertEquals(1, numReceived);
1936 
1937         nextTest(connection, in, out, "Receive byte after some time");
1938         numReceived = connection.bulkTransfer(in, new byte[2], 1, 1, -1);
1939         assertEquals(1, numReceived);
1940     }
1941 
1942     /**
1943      * Test if the companion device zero-terminates their requests that are multiples of the
1944      * maximum package size. Then sets {@link #mDoesCompanionZeroTerminate} if the companion
1945      * zero terminates
1946      *
1947      * @param connection Connection to the USB device
1948      * @param iface      The interface to use
1949      */
testIfCompanionZeroTerminates(@onNull UsbDeviceConnection connection, @NonNull UsbInterface iface)1950     private void testIfCompanionZeroTerminates(@NonNull UsbDeviceConnection connection,
1951             @NonNull UsbInterface iface) {
1952         assertTrue(iface.getEndpointCount() == 2);
1953         final UsbEndpoint in = getEndpoint(iface, UsbConstants.USB_DIR_IN);
1954         final UsbEndpoint out = getEndpoint(iface, UsbConstants.USB_DIR_OUT);
1955         assertNotNull(in);
1956         assertNotNull(out);
1957 
1958         nextTest(connection, in, out, "does companion zero terminate");
1959 
1960         // The other size sends:
1961         // - 1024 bytes
1962         // - maybe a zero sized package
1963         // - 1 byte
1964 
1965         byte[] buffer = new byte[1024];
1966         int numTransferred = connection.bulkTransfer(in, buffer, 1024, 0);
1967         assertEquals(1024, numTransferred);
1968 
1969         numTransferred = connection.bulkTransfer(in, buffer, 1, 0);
1970         if (numTransferred == 0) {
1971             assertEquals(0, numTransferred);
1972 
1973             numTransferred = connection.bulkTransfer(in, buffer, 1, 0);
1974             assertEquals(1, numTransferred);
1975 
1976             mDoesCompanionZeroTerminate = true;
1977             Log.i(LOG_TAG, "Companion zero terminates");
1978         } else {
1979             assertEquals(1, numTransferred);
1980             Log.i(LOG_TAG, "Companion does not zero terminate - an older device");
1981         }
1982     }
1983 
1984     /**
1985      * Send signal to the remove device that testing is finished.
1986      *
1987      * @param connection The connection to use for testing
1988      * @param iface      The interface of the android accessory interface of the device
1989      */
endTesting(@onNull UsbDeviceConnection connection, @NonNull UsbInterface iface)1990     private void endTesting(@NonNull UsbDeviceConnection connection, @NonNull UsbInterface iface) {
1991         // "done" signals that testing is over
1992         nextTest(connection, getEndpoint(iface, UsbConstants.USB_DIR_IN),
1993                 getEndpoint(iface, UsbConstants.USB_DIR_OUT), "done");
1994     }
1995 
1996     /**
1997      * Test the behavior of {@link UsbDeviceConnection#claimInterface} and
1998      * {@link UsbDeviceConnection#releaseInterface}.
1999      *
2000      * <p>Note: The interface under test is <u>not</u> claimed by a kernel driver, hence there is
2001      * no difference in behavior between force and non-force versions of
2002      * {@link UsbDeviceConnection#claimInterface}</p>
2003      *
2004      * @param connection The connection to use
2005      * @param iface The interface to claim and release
2006      *
2007      * @throws Throwable
2008      */
claimInterfaceTests(@onNull UsbDeviceConnection connection, @NonNull UsbInterface iface)2009     private void claimInterfaceTests(@NonNull UsbDeviceConnection connection,
2010             @NonNull UsbInterface iface) throws Throwable {
2011         // The interface is not claimed by the kernel driver, so not forcing it should work
2012         boolean claimed = connection.claimInterface(iface, false);
2013         assertTrue(claimed);
2014         boolean released = connection.releaseInterface(iface);
2015         assertTrue(released);
2016 
2017         // Forcing if it is not necessary does no harm
2018         claimed = connection.claimInterface(iface, true);
2019         assertTrue(claimed);
2020 
2021         // Re-claiming does nothing
2022         claimed = connection.claimInterface(iface, true);
2023         assertTrue(claimed);
2024 
2025         released = connection.releaseInterface(iface);
2026         assertTrue(released);
2027 
2028         // Re-releasing is not allowed
2029         released = connection.releaseInterface(iface);
2030         assertFalse(released);
2031 
2032         // Using an unclaimed interface claims it automatically
2033         int numSent = connection.bulkTransfer(getEndpoint(iface, UsbConstants.USB_DIR_OUT), null, 0,
2034                 0);
2035         assertEquals(0, numSent);
2036 
2037         released = connection.releaseInterface(iface);
2038         assertTrue(released);
2039 
2040         runAndAssertException(() -> connection.claimInterface(null, true),
2041                 NullPointerException.class);
2042         runAndAssertException(() -> connection.claimInterface(null, false),
2043                 NullPointerException.class);
2044         runAndAssertException(() -> connection.releaseInterface(null), NullPointerException.class);
2045     }
2046 
2047     /**
2048      * Test all input parameters to {@link UsbDeviceConnection#setConfiguration} .
2049      *
2050      * <p>Note:
2051      * <ul>
2052      *     <li>The device under test only supports one configuration, hence changing configuration
2053      * is not tested.</li>
2054      *     <li>This test sets the current configuration again. This resets the device.</li>
2055      * </ul></p>
2056      *
2057      * @param device the device under test
2058      *
2059      * @throws Throwable
2060      */
setConfigurationTests(@onNull UsbDevice device)2061     private void setConfigurationTests(@NonNull UsbDevice device) throws Throwable {
2062         // Find the AOAP interface
2063         ArrayList<String> allInterfaces = new ArrayList<>();
2064 
2065         // After getConfiguration the original device already disconnect, after
2066         // test check should use new device and connection
2067         UsbDeviceConnection connection = mUsbManager.openDevice(device);
2068         assertNotNull(connection);
2069 
2070         UsbInterface iface = null;
2071         for (int i = 0; i < device.getConfigurationCount(); i++) {
2072             allInterfaces.add(device.getInterface(i).toString());
2073 
2074             if (device.getInterface(i).getName().equals("Android Accessory Interface")) {
2075                 iface = device.getInterface(i);
2076                 break;
2077             }
2078         }
2079 
2080         // Cannot set configuration for a device with a claimed interface
2081         boolean claimed = connection.claimInterface(iface, false);
2082         assertTrue(claimed);
2083         boolean wasSet = connection.setConfiguration(device.getConfiguration(0));
2084         assertFalse(wasSet);
2085         boolean released = connection.releaseInterface(iface);
2086         assertTrue(released);
2087 
2088         runAndAssertException(() -> connection.setConfiguration(null), NullPointerException.class);
2089     }
2090 
2091     /**
2092      * Test all input parameters to {@link UsbDeviceConnection#setConfiguration} .
2093      *
2094      * <p>Note: The interface under test only supports one settings, hence changing the setting can
2095      * not be tested.</p>
2096      *
2097      * @param connection The connection to use
2098      * @param iface The interface to test
2099      *
2100      * @throws Throwable
2101      */
setInterfaceTests(@onNull UsbDeviceConnection connection, @NonNull UsbInterface iface)2102     private void setInterfaceTests(@NonNull UsbDeviceConnection connection,
2103             @NonNull UsbInterface iface) throws Throwable {
2104         boolean claimed = connection.claimInterface(iface, false);
2105         assertTrue(claimed);
2106         boolean wasSet = connection.setInterface(iface);
2107         assertTrue(wasSet);
2108         boolean released = connection.releaseInterface(iface);
2109         assertTrue(released);
2110 
2111         // Setting the interface for an unclaimed interface automatically claims it
2112         wasSet = connection.setInterface(iface);
2113         assertTrue(wasSet);
2114         released = connection.releaseInterface(iface);
2115         assertTrue(released);
2116 
2117         runAndAssertException(() -> connection.setInterface(null), NullPointerException.class);
2118     }
2119 
2120     /**
2121      * Enumerate all known devices and check basic relationship between the properties.
2122      */
enumerateDevices(@onNull UsbDevice companionDevice)2123     private void enumerateDevices(@NonNull UsbDevice companionDevice) throws Exception {
2124         Set<Integer> knownDeviceIds = new ArraySet<>();
2125 
2126         for (Map.Entry<String, UsbDevice> entry : mUsbManager.getDeviceList().entrySet()) {
2127             UsbDevice device = entry.getValue();
2128 
2129             assertEquals(entry.getKey(), device.getDeviceName());
2130             assertNotNull(device.getDeviceName());
2131 
2132             // Device ID should be unique
2133             assertFalse(knownDeviceIds.contains(device.getDeviceId()));
2134             knownDeviceIds.add(device.getDeviceId());
2135 
2136             assertEquals(device.getDeviceName(), UsbDevice.getDeviceName(device.getDeviceId()));
2137 
2138             // Properties without constraints
2139             device.getManufacturerName();
2140             device.getProductName();
2141             device.getVersion();
2142 
2143             // We are only guaranteed to have permission to the companion device.
2144             if (device.equals(companionDevice)) {
2145                 device.getSerialNumber();
2146             }
2147 
2148             device.getVendorId();
2149             device.getProductId();
2150             device.getDeviceClass();
2151             device.getDeviceSubclass();
2152             device.getDeviceProtocol();
2153 
2154             Set<UsbInterface> interfacesFromAllConfigs = new ArraySet<>();
2155             Set<Integer> knownConfigurationIds = new ArraySet<>();
2156             int numConfigurations = device.getConfigurationCount();
2157             for (int configNum = 0; configNum < numConfigurations; configNum++) {
2158                 UsbConfiguration config = device.getConfiguration(configNum);
2159                 Set<Pair<Integer, Integer>> knownInterfaceIds = new ArraySet<>();
2160 
2161                 // Configuration ID should be unique
2162                 assertFalse(knownConfigurationIds.contains(config.getId()));
2163                 knownConfigurationIds.add(config.getId());
2164 
2165                 assertTrue(config.getMaxPower() >= 0);
2166 
2167                 // Properties without constraints
2168                 config.getName();
2169                 config.isSelfPowered();
2170                 config.isRemoteWakeup();
2171 
2172                 int numInterfaces = config.getInterfaceCount();
2173                 for (int interfaceNum = 0; interfaceNum < numInterfaces; interfaceNum++) {
2174                     UsbInterface iface = config.getInterface(interfaceNum);
2175                     interfacesFromAllConfigs.add(iface);
2176 
2177                     Pair<Integer, Integer> ifaceId = new Pair<>(iface.getId(),
2178                             iface.getAlternateSetting());
2179                     assertFalse(knownInterfaceIds.contains(ifaceId));
2180                     knownInterfaceIds.add(ifaceId);
2181 
2182                     // Properties without constraints
2183                     iface.getName();
2184                     iface.getInterfaceClass();
2185                     iface.getInterfaceSubclass();
2186                     iface.getInterfaceProtocol();
2187 
2188                     int numEndpoints = iface.getEndpointCount();
2189                     for (int endpointNum = 0; endpointNum < numEndpoints; endpointNum++) {
2190                         UsbEndpoint endpoint = iface.getEndpoint(endpointNum);
2191 
2192                         assertEquals(endpoint.getAddress(),
2193                                 endpoint.getEndpointNumber() | endpoint.getDirection());
2194 
2195                         assertTrue(endpoint.getDirection() == UsbConstants.USB_DIR_OUT ||
2196                                 endpoint.getDirection() == UsbConstants.USB_DIR_IN);
2197 
2198                         assertTrue(endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_CONTROL ||
2199                                 endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_ISOC ||
2200                                 endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK ||
2201                                 endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_INT);
2202 
2203                         assertTrue(endpoint.getMaxPacketSize() >= 0);
2204                         assertTrue(endpoint.getInterval() >= 0);
2205 
2206                         // Properties without constraints
2207                         endpoint.getAttributes();
2208                     }
2209                 }
2210             }
2211 
2212             int numInterfaces = device.getInterfaceCount();
2213             for (int interfaceNum = 0; interfaceNum < numInterfaces; interfaceNum++) {
2214                 assertTrue(interfacesFromAllConfigs.contains(device.getInterface(interfaceNum)));
2215             }
2216         }
2217     }
2218 
2219     /**
2220      * Run tests.
2221      *
2222      * @param device The device to run the test against. This device is running
2223      *               com.android.cts.verifierusbcompanion.DeviceTestCompanion
2224      */
runTests(@onNull UsbDevice device)2225     private void runTests(@NonNull UsbDevice device) {
2226         try {
2227             // Find the AOAP interface
2228             ArrayList<String> allInterfaces = new ArrayList<>();
2229 
2230             // Errors created in the threads
2231             ArrayList<Throwable> errors = new ArrayList<>();
2232 
2233             // Reconnect should get attached intent and pass test in 10 seconds
2234             long attachedTime = 10 * 1000;
2235 
2236             UsbInterface iface = null;
2237             for (int i = 0; i < device.getConfigurationCount(); i++) {
2238                 allInterfaces.add(device.getInterface(i).toString());
2239 
2240                 if (device.getInterface(i).getName().equals("Android Accessory Interface")) {
2241                     iface = device.getInterface(i);
2242                     break;
2243                 }
2244             }
2245             assertNotNull("No \"Android Accessory Interface\" interface found in " + allInterfaces,
2246                     iface);
2247 
2248             enumerateDevices(device);
2249 
2250             UsbDeviceConnection connection = mUsbManager.openDevice(device);
2251             assertNotNull(connection);
2252 
2253             claimInterfaceTests(connection, iface);
2254 
2255             boolean claimed = connection.claimInterface(iface, false);
2256             assertTrue(claimed);
2257 
2258             testIfCompanionZeroTerminates(connection, iface);
2259 
2260             usbRequestLegacyTests(connection, iface);
2261             usbRequestTests(connection, iface);
2262             parallelUsbRequestsTests(connection, iface);
2263             ctrlTransferTests(connection);
2264             bulkTransferTests(connection, iface);
2265 
2266             // Signal to the DeviceTestCompanion that there are no more transfer test
2267             endTesting(connection, iface);
2268             boolean released = connection.releaseInterface(iface);
2269             assertTrue(released);
2270 
2271             CompletableFuture<ArrayList<Throwable>> attached =
2272                     CompletableFuture.supplyAsync(() -> {
2273                         return attachedTask();
2274                     });
2275 
2276             setInterfaceTests(connection, iface);
2277 
2278             assertTrue(device.getConfigurationCount() == 1);
2279             assertTrue(connection.setConfiguration(device.getConfiguration(0)));
2280 
2281             errors = attached.get(attachedTime, TimeUnit.MILLISECONDS);
2282 
2283             // If reconnect timeout make the test fail
2284             assertEquals(0, errors.size());
2285 
2286             // Update connection handle after reconnect
2287             device = getReconnectDevice();
2288             assertNotNull(device);
2289             connection = mUsbManager.openDevice(device);
2290             assertNotNull(connection);
2291 
2292             connection.close();
2293 
2294             // We should not be able to communicate with the device anymore
2295             assertFalse(connection.claimInterface(iface, true));
2296             assertFalse(connection.releaseInterface(iface));
2297             assertFalse(connection.setConfiguration(device.getConfiguration(0)));
2298             assertFalse(connection.setInterface(iface));
2299             assertTrue(connection.getFileDescriptor() == -1);
2300             assertNull(connection.getRawDescriptors());
2301             assertNull(connection.getSerial());
2302             assertEquals(-1, connection.bulkTransfer(getEndpoint(iface, UsbConstants.USB_DIR_OUT),
2303                     new byte[1], 1, 0));
2304             assertEquals(-1, connection.bulkTransfer(getEndpoint(iface, UsbConstants.USB_DIR_OUT),
2305                     null, 0, 0));
2306             assertEquals(-1, connection.bulkTransfer(getEndpoint(iface, UsbConstants.USB_DIR_IN),
2307                     null, 0, 0));
2308             assertFalse((new UsbRequest()).initialize(connection, getEndpoint(iface,
2309                     UsbConstants.USB_DIR_IN)));
2310 
2311             // Double close should do no harm
2312             connection.close();
2313 
2314             setTestResultAndFinish(true);
2315         } catch (Throwable e) {
2316             fail(null, e);
2317         }
2318     }
2319 
2320     @Override
onDestroy()2321     protected void onDestroy() {
2322         if (mUsbDeviceConnectionReceiver != null) {
2323             unregisterReceiver(mUsbDeviceConnectionReceiver);
2324         }
2325         if (mUsbDeviceAttachedReceiver != null) {
2326             unregisterReceiver(mUsbDeviceAttachedReceiver);
2327         }
2328         if (mUsbDevicePermissionReceiver != null) {
2329             unregisterReceiver(mUsbDevicePermissionReceiver);
2330         }
2331 
2332         super.onDestroy();
2333     }
2334 }
2335