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