451 lines
17 KiB
Java
451 lines
17 KiB
Java
![]() |
/*
|
||
|
* Copyright (C) 2010 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 android.hardware.usb;
|
||
|
|
||
|
import android.annotation.Nullable;
|
||
|
import android.compat.annotation.UnsupportedAppUsage;
|
||
|
import android.os.Build;
|
||
|
import android.util.Log;
|
||
|
|
||
|
import com.android.internal.util.Preconditions;
|
||
|
|
||
|
import dalvik.system.CloseGuard;
|
||
|
|
||
|
import java.nio.BufferOverflowException;
|
||
|
import java.nio.ByteBuffer;
|
||
|
import java.util.Objects;
|
||
|
|
||
|
/**
|
||
|
* A class representing USB request packet.
|
||
|
* This can be used for both reading and writing data to or from a
|
||
|
* {@link android.hardware.usb.UsbDeviceConnection}.
|
||
|
* UsbRequests can be used to transfer data on bulk and interrupt endpoints.
|
||
|
* Requests on bulk endpoints can be sent synchronously via {@link UsbDeviceConnection#bulkTransfer}
|
||
|
* or asynchronously via {@link #queue} and {@link UsbDeviceConnection#requestWait}.
|
||
|
* Requests on interrupt endpoints are only send and received asynchronously.
|
||
|
*
|
||
|
* <p>Requests on endpoint zero are not supported by this class;
|
||
|
* use {@link UsbDeviceConnection#controlTransfer} for endpoint zero requests instead.
|
||
|
*/
|
||
|
public class UsbRequest {
|
||
|
|
||
|
private static final String TAG = "UsbRequest";
|
||
|
|
||
|
// From drivers/usb/core/devio.c
|
||
|
static final int MAX_USBFS_BUFFER_SIZE = 16384;
|
||
|
|
||
|
// used by the JNI code
|
||
|
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
|
||
|
private long mNativeContext;
|
||
|
|
||
|
private UsbEndpoint mEndpoint;
|
||
|
|
||
|
/** The buffer that is currently being read / written */
|
||
|
@UnsupportedAppUsage
|
||
|
private ByteBuffer mBuffer;
|
||
|
|
||
|
/** The amount of data to read / write when using {@link #queue} */
|
||
|
@UnsupportedAppUsage
|
||
|
private int mLength;
|
||
|
|
||
|
// for client use
|
||
|
private Object mClientData;
|
||
|
|
||
|
// Prevent the connection from being finalized
|
||
|
private UsbDeviceConnection mConnection;
|
||
|
|
||
|
/**
|
||
|
* Whether this buffer was {@link #queue(ByteBuffer) queued using the new behavior} or
|
||
|
* {@link #queue(ByteBuffer, int) queued using the deprecated behavior}.
|
||
|
*/
|
||
|
private boolean mIsUsingNewQueue;
|
||
|
|
||
|
/** Temporary buffer than might be used while buffer is enqueued */
|
||
|
private ByteBuffer mTempBuffer;
|
||
|
|
||
|
private final CloseGuard mCloseGuard = CloseGuard.get();
|
||
|
|
||
|
/**
|
||
|
* Lock for queue, enqueue and dequeue, so a queue operation can be finished by a dequeue
|
||
|
* operation on a different thread.
|
||
|
*/
|
||
|
private final Object mLock = new Object();
|
||
|
|
||
|
public UsbRequest() {
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Initializes the request so it can read or write data on the given endpoint.
|
||
|
* Whether the request allows reading or writing depends on the direction of the endpoint.
|
||
|
*
|
||
|
* @param endpoint the endpoint to be used for this request.
|
||
|
* @return true if the request was successfully opened.
|
||
|
*/
|
||
|
public boolean initialize(UsbDeviceConnection connection, UsbEndpoint endpoint) {
|
||
|
mEndpoint = endpoint;
|
||
|
mConnection = Objects.requireNonNull(connection, "connection");
|
||
|
|
||
|
boolean wasInitialized = native_init(connection, endpoint.getAddress(),
|
||
|
endpoint.getAttributes(), endpoint.getMaxPacketSize(), endpoint.getInterval());
|
||
|
|
||
|
if (wasInitialized) {
|
||
|
mCloseGuard.open("UsbRequest.close");
|
||
|
}
|
||
|
|
||
|
return wasInitialized;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Releases all resources related to this request.
|
||
|
*/
|
||
|
public void close() {
|
||
|
synchronized (mLock) {
|
||
|
if (mNativeContext != 0) {
|
||
|
mEndpoint = null;
|
||
|
mConnection = null;
|
||
|
native_close();
|
||
|
mCloseGuard.close();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
@Override
|
||
|
protected void finalize() throws Throwable {
|
||
|
try {
|
||
|
if (mCloseGuard != null) {
|
||
|
mCloseGuard.warnIfOpen();
|
||
|
}
|
||
|
|
||
|
close();
|
||
|
} finally {
|
||
|
super.finalize();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the endpoint for the request, or null if the request is not opened.
|
||
|
*
|
||
|
* @return the request's endpoint
|
||
|
*/
|
||
|
public UsbEndpoint getEndpoint() {
|
||
|
return mEndpoint;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Returns the client data for the request.
|
||
|
* This can be used in conjunction with {@link #setClientData}
|
||
|
* to associate another object with this request, which can be useful for
|
||
|
* maintaining state between calls to {@link #queue} and
|
||
|
* {@link android.hardware.usb.UsbDeviceConnection#requestWait}
|
||
|
*
|
||
|
* @return the client data for the request
|
||
|
*/
|
||
|
public Object getClientData() {
|
||
|
return mClientData;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Sets the client data for the request.
|
||
|
* This can be used in conjunction with {@link #getClientData}
|
||
|
* to associate another object with this request, which can be useful for
|
||
|
* maintaining state between calls to {@link #queue} and
|
||
|
* {@link android.hardware.usb.UsbDeviceConnection#requestWait}
|
||
|
*
|
||
|
* @param data the client data for the request
|
||
|
*/
|
||
|
public void setClientData(Object data) {
|
||
|
mClientData = data;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Queues the request to send or receive data on its endpoint.
|
||
|
* <p>For OUT endpoints, the given buffer data will be sent on the endpoint. For IN endpoints,
|
||
|
* the endpoint will attempt to read the given number of bytes into the specified buffer. If the
|
||
|
* queueing operation is successful, return true. The result will be returned via
|
||
|
* {@link UsbDeviceConnection#requestWait}</p>
|
||
|
*
|
||
|
* @param buffer the buffer containing the bytes to write, or location to store the results of a
|
||
|
* read. Position and array offset will be ignored and assumed to be 0. Limit and
|
||
|
* capacity will be ignored. Once the request
|
||
|
* {@link UsbDeviceConnection#requestWait() is processed} the position will be set
|
||
|
* to the number of bytes read/written.
|
||
|
* @param length number of bytes to read or write. Before {@value Build.VERSION_CODES#P}, a
|
||
|
* value larger than 16384 bytes would be truncated down to 16384. In API
|
||
|
* {@value Build.VERSION_CODES#P} and after, any value of length is valid.
|
||
|
*
|
||
|
* @return true if the queueing operation succeeded
|
||
|
*
|
||
|
* @deprecated Use {@link #queue(ByteBuffer)} instead.
|
||
|
*/
|
||
|
@Deprecated
|
||
|
public boolean queue(ByteBuffer buffer, int length) {
|
||
|
UsbDeviceConnection connection = mConnection;
|
||
|
if (connection == null) {
|
||
|
// The expected exception by CTS Verifier - USB Device test
|
||
|
throw new NullPointerException("invalid connection");
|
||
|
}
|
||
|
|
||
|
// Calling into the underlying UsbDeviceConnection to synchronize on its lock, to prevent
|
||
|
// the connection being closed while queueing.
|
||
|
return connection.queueRequest(this, buffer, length);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This is meant to be called from UsbDeviceConnection after synchronizing using the lock over
|
||
|
* there, to prevent the connection being closed while queueing.
|
||
|
*/
|
||
|
/* package */ boolean queueIfConnectionOpen(ByteBuffer buffer, int length) {
|
||
|
UsbDeviceConnection connection = mConnection;
|
||
|
if (connection == null || !connection.isOpen()) {
|
||
|
// The expected exception by CTS Verifier - USB Device test
|
||
|
throw new NullPointerException("invalid connection");
|
||
|
}
|
||
|
|
||
|
boolean out = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT);
|
||
|
boolean result;
|
||
|
|
||
|
if (connection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P
|
||
|
&& length > MAX_USBFS_BUFFER_SIZE) {
|
||
|
length = MAX_USBFS_BUFFER_SIZE;
|
||
|
}
|
||
|
|
||
|
synchronized (mLock) {
|
||
|
// save our buffer for when the request has completed
|
||
|
mBuffer = buffer;
|
||
|
mLength = length;
|
||
|
|
||
|
// Note: On a buffer slice we lost the capacity information about the underlying buffer,
|
||
|
// hence we cannot check if the access would be a data leak/memory corruption.
|
||
|
|
||
|
if (buffer.isDirect()) {
|
||
|
result = native_queue_direct(buffer, length, out);
|
||
|
} else if (buffer.hasArray()) {
|
||
|
result = native_queue_array(buffer.array(), length, out);
|
||
|
} else {
|
||
|
throw new IllegalArgumentException("buffer is not direct and has no array");
|
||
|
}
|
||
|
if (!result) {
|
||
|
mBuffer = null;
|
||
|
mLength = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return result;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Queues the request to send or receive data on its endpoint.
|
||
|
*
|
||
|
* <p>For OUT endpoints, the remaining bytes of the buffer will be sent on the endpoint. For IN
|
||
|
* endpoints, the endpoint will attempt to fill the remaining bytes of the buffer. If the
|
||
|
* queueing operation is successful, return true. The result will be returned via
|
||
|
* {@link UsbDeviceConnection#requestWait}</p>
|
||
|
*
|
||
|
* @param buffer the buffer containing the bytes to send, or the buffer to fill. The state
|
||
|
* of the buffer is undefined until the request is returned by
|
||
|
* {@link UsbDeviceConnection#requestWait}. If the request failed the buffer
|
||
|
* will be unchanged; if the request succeeded the position of the buffer is
|
||
|
* incremented by the number of bytes sent/received. Before
|
||
|
* {@value Build.VERSION_CODES#P}, a buffer of length larger than 16384 bytes
|
||
|
* would throw IllegalArgumentException. In API {@value Build.VERSION_CODES#P}
|
||
|
* and after, any size buffer is valid.
|
||
|
*
|
||
|
* @return true if the queueing operation succeeded
|
||
|
*/
|
||
|
public boolean queue(@Nullable ByteBuffer buffer) {
|
||
|
UsbDeviceConnection connection = mConnection;
|
||
|
if (connection == null) {
|
||
|
// The expected exception by CTS Verifier - USB Device test
|
||
|
throw new IllegalStateException("invalid connection");
|
||
|
}
|
||
|
|
||
|
// Calling into the underlying UsbDeviceConnection to synchronize on its lock, to prevent
|
||
|
// the connection being closed while queueing.
|
||
|
return connection.queueRequest(this, buffer);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* This is meant to be called from UsbDeviceConnection after synchronizing using the lock over
|
||
|
* there, to prevent the connection being closed while queueing.
|
||
|
*/
|
||
|
/* package */ boolean queueIfConnectionOpen(@Nullable ByteBuffer buffer) {
|
||
|
UsbDeviceConnection connection = mConnection;
|
||
|
if (connection == null || !connection.isOpen()) {
|
||
|
// The expected exception by CTS Verifier - USB Device test
|
||
|
throw new IllegalStateException("invalid connection");
|
||
|
}
|
||
|
|
||
|
// Request need to be initialized
|
||
|
Preconditions.checkState(mNativeContext != 0, "request is not initialized");
|
||
|
|
||
|
// Request can not be currently queued
|
||
|
Preconditions.checkState(!mIsUsingNewQueue, "this request is currently queued");
|
||
|
|
||
|
boolean isSend = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT);
|
||
|
boolean wasQueued;
|
||
|
|
||
|
synchronized (mLock) {
|
||
|
mBuffer = buffer;
|
||
|
|
||
|
if (buffer == null) {
|
||
|
// Null buffers enqueue empty USB requests which is supported
|
||
|
mIsUsingNewQueue = true;
|
||
|
wasQueued = native_queue(null, 0, 0);
|
||
|
} else {
|
||
|
if (connection.getContext().getApplicationInfo().targetSdkVersion
|
||
|
< Build.VERSION_CODES.P) {
|
||
|
// Can only send/receive MAX_USBFS_BUFFER_SIZE bytes at once
|
||
|
Preconditions.checkArgumentInRange(buffer.remaining(), 0, MAX_USBFS_BUFFER_SIZE,
|
||
|
"number of remaining bytes");
|
||
|
}
|
||
|
|
||
|
// Can not receive into read-only buffers.
|
||
|
Preconditions.checkArgument(!(buffer.isReadOnly() && !isSend), "buffer can not be "
|
||
|
+ "read-only when receiving data");
|
||
|
|
||
|
if (!buffer.isDirect()) {
|
||
|
mTempBuffer = ByteBuffer.allocateDirect(mBuffer.remaining());
|
||
|
|
||
|
if (isSend) {
|
||
|
// Copy buffer into temporary buffer
|
||
|
mBuffer.mark();
|
||
|
mTempBuffer.put(mBuffer);
|
||
|
mTempBuffer.flip();
|
||
|
mBuffer.reset();
|
||
|
}
|
||
|
|
||
|
// Send/Receive into the temp buffer instead
|
||
|
buffer = mTempBuffer;
|
||
|
}
|
||
|
|
||
|
mIsUsingNewQueue = true;
|
||
|
wasQueued = native_queue(buffer, buffer.position(), buffer.remaining());
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!wasQueued) {
|
||
|
mIsUsingNewQueue = false;
|
||
|
mTempBuffer = null;
|
||
|
mBuffer = null;
|
||
|
}
|
||
|
|
||
|
return wasQueued;
|
||
|
}
|
||
|
|
||
|
/* package */ void dequeue(boolean useBufferOverflowInsteadOfIllegalArg) {
|
||
|
boolean isSend = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT);
|
||
|
int bytesTransferred;
|
||
|
|
||
|
synchronized (mLock) {
|
||
|
if (mIsUsingNewQueue) {
|
||
|
bytesTransferred = native_dequeue_direct();
|
||
|
mIsUsingNewQueue = false;
|
||
|
|
||
|
if (mBuffer == null) {
|
||
|
// Nothing to do
|
||
|
} else if (mTempBuffer == null) {
|
||
|
mBuffer.position(mBuffer.position() + bytesTransferred);
|
||
|
} else {
|
||
|
mTempBuffer.limit(bytesTransferred);
|
||
|
|
||
|
// The user might have modified mBuffer which might make put/position fail.
|
||
|
// Changing the buffer while a request is in flight is not supported. Still,
|
||
|
// make sure to free mTempBuffer correctly.
|
||
|
try {
|
||
|
if (isSend) {
|
||
|
mBuffer.position(mBuffer.position() + bytesTransferred);
|
||
|
} else {
|
||
|
// Copy temp buffer back into original buffer
|
||
|
mBuffer.put(mTempBuffer);
|
||
|
}
|
||
|
} finally {
|
||
|
mTempBuffer = null;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
if (mBuffer.isDirect()) {
|
||
|
bytesTransferred = native_dequeue_direct();
|
||
|
} else {
|
||
|
bytesTransferred = native_dequeue_array(mBuffer.array(), mLength, isSend);
|
||
|
}
|
||
|
if (bytesTransferred >= 0) {
|
||
|
int bytesToStore = Math.min(bytesTransferred, mLength);
|
||
|
try {
|
||
|
mBuffer.position(bytesToStore);
|
||
|
} catch (IllegalArgumentException e) {
|
||
|
if (useBufferOverflowInsteadOfIllegalArg) {
|
||
|
Log.e(TAG, "Buffer " + mBuffer + " does not have enough space to read "
|
||
|
+ bytesToStore + " bytes", e);
|
||
|
throw new BufferOverflowException();
|
||
|
} else {
|
||
|
throw e;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
mBuffer = null;
|
||
|
mLength = 0;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Cancels a pending queue operation.
|
||
|
*
|
||
|
* @return true if cancelling succeeded
|
||
|
*/
|
||
|
public boolean cancel() {
|
||
|
UsbDeviceConnection connection = mConnection;
|
||
|
if (connection == null) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
return connection.cancelRequest(this);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Cancels a pending queue operation (for use when the UsbDeviceConnection associated
|
||
|
* with this request is synchronized). This ensures we don't have a race where the
|
||
|
* device is closed and then the request is canceled which would lead to a
|
||
|
* use-after-free because the cancel operation uses the device connection
|
||
|
* information freed in the when UsbDeviceConnection is closed.<br/>
|
||
|
*
|
||
|
* This method assumes the connected is not closed while this method is executed.
|
||
|
*
|
||
|
* @return true if cancelling succeeded.
|
||
|
*/
|
||
|
/* package */ boolean cancelIfOpen() {
|
||
|
UsbDeviceConnection connection = mConnection;
|
||
|
if (mNativeContext == 0 || (connection != null && !connection.isOpen())) {
|
||
|
Log.w(TAG,
|
||
|
"Detected attempt to cancel a request on a connection which isn't open");
|
||
|
return false;
|
||
|
}
|
||
|
return native_cancel();
|
||
|
}
|
||
|
|
||
|
private native boolean native_init(UsbDeviceConnection connection, int ep_address,
|
||
|
int ep_attributes, int ep_max_packet_size, int ep_interval);
|
||
|
private native void native_close();
|
||
|
private native boolean native_queue(ByteBuffer buffer, int offset, int length);
|
||
|
private native boolean native_queue_array(byte[] buffer, int length, boolean out);
|
||
|
private native int native_dequeue_array(byte[] buffer, int length, boolean out);
|
||
|
private native boolean native_queue_direct(ByteBuffer buffer, int length, boolean out);
|
||
|
private native int native_dequeue_direct();
|
||
|
private native boolean native_cancel();
|
||
|
}
|