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