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