| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.cts.verifierusbcompanion; |
| |
| import static org.junit.Assert.assertEquals; |
| |
| import android.app.PendingIntent; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.hardware.usb.UsbConstants; |
| import android.hardware.usb.UsbDevice; |
| import android.hardware.usb.UsbDeviceConnection; |
| import android.hardware.usb.UsbEndpoint; |
| import android.hardware.usb.UsbInterface; |
| import android.hardware.usb.UsbManager; |
| import android.support.annotation.NonNull; |
| |
| import java.nio.ByteBuffer; |
| import java.nio.charset.Charset; |
| |
| /** |
| * Companion code for com.android.cts.verifier.usb.device.UsbAccessoryTestActivity |
| */ |
| class AccessoryTestCompanion extends TestCompanion { |
| private static final int TIMEOUT_MILLIS = 500; |
| private static final int MAX_BUFFER_SIZE = 16384; |
| private static final int TEST_DATA_SIZE_THRESHOLD = 100 * 1024 * 1024; // 100MB |
| |
| private static final String ACTION_USB_PERMISSION = |
| "com.android.cts.verifierusbcompanion.USB_PERMISSION"; |
| |
| private UsbManager mUsbManager; |
| private BroadcastReceiver mUsbDeviceConnectionReceiver; |
| private UsbDevice mDevice; |
| |
| AccessoryTestCompanion(@NonNull Context context, @NonNull TestObserver observer) { |
| super(context, observer); |
| } |
| |
| /** |
| * @throws Throwable |
| */ |
| @Override |
| protected void runTest() throws Throwable { |
| updateStatus("Waiting for device under test to connect"); |
| |
| mUsbManager = getContext().getSystemService(UsbManager.class); |
| |
| mUsbDeviceConnectionReceiver = new BroadcastReceiver() { |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| synchronized (AccessoryTestCompanion.this) { |
| UsbDevice device = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE); |
| |
| switch (intent.getAction()) { |
| case UsbManager.ACTION_USB_DEVICE_ATTACHED: |
| if (mUsbManager.hasPermission(device)) { |
| onDeviceAccessPermitted(device); |
| } else { |
| mUsbManager.requestPermission(device, |
| PendingIntent.getBroadcast(getContext(), 0, |
| new Intent(ACTION_USB_PERMISSION), 0)); |
| } |
| break; |
| case ACTION_USB_PERMISSION: |
| boolean granted = intent.getBooleanExtra( |
| UsbManager.EXTRA_PERMISSION_GRANTED, false); |
| |
| if (granted) { |
| onDeviceAccessPermitted(device); |
| } else { |
| fail("Permission to connect to " + device.getProductName() |
| + " not granted"); |
| } |
| break; |
| } |
| } |
| } |
| }; |
| |
| IntentFilter filter = new IntentFilter(); |
| filter.addAction(ACTION_USB_PERMISSION); |
| filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED); |
| |
| getContext().registerReceiver(mUsbDeviceConnectionReceiver, filter); |
| |
| synchronized (this) { |
| while (mDevice == null) { |
| wait(); |
| } |
| } |
| |
| UsbInterface iface = null; |
| for (int i = 0; i < mDevice.getConfigurationCount(); i++) { |
| if (mDevice.getInterface(i).getName().equals("Android Accessory Interface")) { |
| iface = mDevice.getInterface(i); |
| break; |
| } |
| } |
| |
| UsbEndpoint in = getEndpoint(iface, UsbConstants.USB_DIR_IN); |
| UsbEndpoint out = getEndpoint(iface, UsbConstants.USB_DIR_OUT); |
| |
| UsbDeviceConnection connection = mUsbManager.openDevice(mDevice); |
| |
| try { |
| String testName; |
| do { |
| testName = nextTest(connection, in, out, true); |
| |
| updateStatus("Running test \"" + testName + "\""); |
| |
| switch (testName) { |
| case "echo 32 bytes": { |
| byte[] buffer = new byte[32]; |
| |
| int numTransferred = connection.bulkTransfer(in, buffer, 32, 0); |
| assertEquals(32, numTransferred); |
| |
| numTransferred = connection.bulkTransfer(out, buffer, 32, 0); |
| assertEquals(32, numTransferred); |
| } |
| break; |
| |
| case "echo two 16 byte transfers as one": { |
| byte[] buffer = new byte[48]; |
| |
| // We receive the individual transfers even if we wait for more data |
| int numTransferred = connection.bulkTransfer(in, buffer, 32, 0); |
| assertEquals(16, numTransferred); |
| numTransferred = connection.bulkTransfer(in, buffer, 16, 32, 0); |
| assertEquals(16, numTransferred); |
| |
| numTransferred = connection.bulkTransfer(out, buffer, 32, 0); |
| assertEquals(32, numTransferred); |
| } |
| break; |
| |
| case "echo 32 bytes as two 16 byte transfers": { |
| byte[] buffer = new byte[32]; |
| |
| int numTransferred = connection.bulkTransfer(in, buffer, 32, 0); |
| assertEquals(32, numTransferred); |
| |
| numTransferred = connection.bulkTransfer(out, buffer, 16, 0); |
| assertEquals(16, numTransferred); |
| numTransferred = connection.bulkTransfer(out, buffer, 16, 16, 0); |
| assertEquals(16, numTransferred); |
| } |
| break; |
| |
| case "measure out transfer speed": { |
| byte[] buffer = new byte[MAX_BUFFER_SIZE]; |
| |
| long bytesRead = 0; |
| while (bytesRead < TEST_DATA_SIZE_THRESHOLD) { |
| int numTransferred = connection.bulkTransfer( |
| in, buffer, MAX_BUFFER_SIZE, 0); |
| bytesRead += numTransferred; |
| } |
| |
| // MAX_BUFFER_SIZE is a multiple of the package size, hence we get a zero |
| // sized package after. Some older devices do not send these packages, but |
| // this is not compliant anymore. |
| int numTransferred = connection.bulkTransfer(in, buffer, 1, TIMEOUT_MILLIS); |
| assertEquals(0, numTransferred); |
| |
| byte[] confirm = new byte[] {1}; |
| numTransferred = connection.bulkTransfer(out, confirm, 1, 0); |
| assertEquals(1, numTransferred); |
| } |
| break; |
| |
| case "measure in transfer speed": { |
| byte[] buffer = new byte[MAX_BUFFER_SIZE]; |
| |
| long bytesWritten = 0; |
| int numTransferred = 0; |
| while (bytesWritten < TEST_DATA_SIZE_THRESHOLD) { |
| numTransferred = |
| connection.bulkTransfer(out, buffer, MAX_BUFFER_SIZE, 0); |
| assertEquals(MAX_BUFFER_SIZE, numTransferred); |
| bytesWritten += numTransferred; |
| } |
| |
| byte[] confirm = new byte[] {1}; |
| numTransferred = connection.bulkTransfer(out, confirm, 1, 0); |
| assertEquals(1, numTransferred); |
| } |
| break; |
| |
| case "echo max bytes": { |
| byte[] buffer = new byte[MAX_BUFFER_SIZE]; |
| |
| int numTransferred = connection.bulkTransfer(in, buffer, MAX_BUFFER_SIZE, |
| 0); |
| assertEquals(MAX_BUFFER_SIZE, numTransferred); |
| |
| // MAX_BUFFER_SIZE is a multiple of the package size, hence we get a zero |
| // sized package after. Some older devices do not send these packages, but |
| // this is not compliant anymore. |
| numTransferred = connection.bulkTransfer(in, buffer, 1, TIMEOUT_MILLIS); |
| assertEquals(0, numTransferred); |
| |
| numTransferred = connection.bulkTransfer(out, buffer, MAX_BUFFER_SIZE, 0); |
| assertEquals(MAX_BUFFER_SIZE, numTransferred); |
| } |
| break; |
| |
| case "echo max*2 bytes": { |
| byte[] buffer = new byte[MAX_BUFFER_SIZE * 2]; |
| |
| int numTransferred = connection.bulkTransfer(in, buffer, MAX_BUFFER_SIZE, |
| 0); |
| assertEquals(MAX_BUFFER_SIZE, numTransferred); |
| |
| // Oversized transfers get split into two |
| numTransferred = connection.bulkTransfer(in, buffer, MAX_BUFFER_SIZE, |
| MAX_BUFFER_SIZE, 0); |
| assertEquals(MAX_BUFFER_SIZE, numTransferred); |
| |
| // MAX_BUFFER_SIZE is a multiple of the package size, hence we get a zero |
| // sized package after. Some older devices do not send these packages, but |
| // this is not compliant anymore. |
| numTransferred = connection.bulkTransfer(in, buffer, 1, TIMEOUT_MILLIS); |
| assertEquals(0, numTransferred); |
| |
| numTransferred = connection.bulkTransfer(out, buffer, MAX_BUFFER_SIZE, 100); |
| assertEquals(MAX_BUFFER_SIZE, numTransferred); |
| |
| numTransferred = connection.bulkTransfer(out, buffer, MAX_BUFFER_SIZE, |
| MAX_BUFFER_SIZE, 0); |
| assertEquals(MAX_BUFFER_SIZE, numTransferred); |
| } |
| break; |
| |
| default: |
| break; |
| } |
| } while (!testName.equals("done")); |
| } finally { |
| connection.close(); |
| } |
| } |
| |
| /** |
| * If access to a device was permitted either make the device an accessory if it already is, |
| * start the test. |
| * |
| * @param device The device access was permitted to |
| */ |
| private void onDeviceAccessPermitted(@NonNull UsbDevice device) { |
| if (!AoapInterface.isDeviceInAoapMode(device)) { |
| UsbDeviceConnection connection = mUsbManager.openDevice(device); |
| try { |
| makeThisDeviceAnAccessory(connection); |
| } finally { |
| connection.close(); |
| } |
| } else { |
| getContext().unregisterReceiver(mUsbDeviceConnectionReceiver); |
| mUsbDeviceConnectionReceiver = null; |
| |
| synchronized (AccessoryTestCompanion.this) { |
| mDevice = device; |
| |
| AccessoryTestCompanion.this.notifyAll(); |
| } |
| } |
| } |
| |
| @NonNull private String nextTest(@NonNull UsbDeviceConnection connection, |
| @NonNull UsbEndpoint in, @NonNull UsbEndpoint out, boolean isSuccess) { |
| byte[] sizeBuffer = new byte[1]; |
| |
| updateStatus("Waiting for next test"); |
| |
| int numTransferred = connection.bulkTransfer(in, sizeBuffer, 1, 0); |
| assertEquals(1, numTransferred); |
| |
| int nameSize = sizeBuffer[0]; |
| |
| byte[] nameBuffer = new byte[nameSize]; |
| numTransferred = connection.bulkTransfer(in, nameBuffer, nameSize, 0); |
| assertEquals(nameSize, numTransferred); |
| |
| numTransferred = connection.bulkTransfer(out, new byte[]{(byte) (isSuccess ? 1 : 0)}, 1, 0); |
| assertEquals(1, numTransferred); |
| |
| numTransferred = connection.bulkTransfer(in, new byte[1], 1, 0); |
| assertEquals(1, numTransferred); |
| |
| String name = Charset.forName("UTF-8").decode(ByteBuffer.wrap(nameBuffer)).toString(); |
| |
| updateStatus("Next test is " + name); |
| |
| return name; |
| } |
| |
| /** |
| * Search an {@link UsbInterface} for an {@link UsbEndpoint endpoint} of a certain direction. |
| * |
| * @param iface The interface to search |
| * @param direction The direction the endpoint is for. |
| * |
| * @return The first endpoint found or {@link null}. |
| */ |
| @NonNull private UsbEndpoint getEndpoint(@NonNull UsbInterface iface, int direction) { |
| for (int i = 0; i < iface.getEndpointCount(); i++) { |
| UsbEndpoint ep = iface.getEndpoint(i); |
| if (ep.getDirection() == direction) { |
| return ep; |
| } |
| } |
| |
| throw new IllegalStateException("Could not find " + direction + " endpoint in " |
| + iface.getName()); |
| } |
| |
| /** |
| * Converts the device under test into an Android accessory. Accessories are USB hosts that are |
| * detected on the device side via {@link UsbManager#getAccessoryList()}. |
| * |
| * @param connection The connection to the USB device |
| */ |
| private void makeThisDeviceAnAccessory(@NonNull UsbDeviceConnection connection) { |
| AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_MANUFACTURER, |
| "Android"); |
| AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_MODEL, |
| "Android device"); |
| AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_DESCRIPTION, |
| "Android device running CTS verifier"); |
| AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_VERSION, "1"); |
| AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_URI, |
| "https://source.android.com/compatibility/cts/verifier.html"); |
| AoapInterface.sendString(connection, AoapInterface.ACCESSORY_STRING_SERIAL, "0"); |
| AoapInterface.sendAoapStart(connection); |
| } |
| } |