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