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