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