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