| /* |
| * 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.util.Log; |
| |
| import com.android.internal.util.Preconditions; |
| |
| import dalvik.system.CloseGuard; |
| |
| import java.nio.BufferOverflowException; |
| import java.nio.ByteBuffer; |
| |
| /** |
| * 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 |
| private static final int MAX_USBFS_BUFFER_SIZE = 16384; |
| |
| // used by the JNI code |
| private long mNativeContext; |
| |
| private UsbEndpoint mEndpoint; |
| |
| /** The buffer that is currently being read / written */ |
| private ByteBuffer mBuffer; |
| |
| /** The amount of data to read / write when using {@link #queue} */ |
| 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 = Preconditions.checkNotNull(connection, "connection"); |
| |
| boolean wasInitialized = native_init(connection, endpoint.getAddress(), |
| endpoint.getAttributes(), endpoint.getMaxPacketSize(), endpoint.getInterval()); |
| |
| if (wasInitialized) { |
| mCloseGuard.open("close"); |
| } |
| |
| return wasInitialized; |
| } |
| |
| /** |
| * Releases all resources related to this request. |
| */ |
| public void close() { |
| 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. |
| * |
| * @return true if the queueing operation succeeded |
| * |
| * @deprecated Use {@link #queue(ByteBuffer)} instead. |
| */ |
| @Deprecated |
| public boolean queue(ByteBuffer buffer, int length) { |
| boolean out = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT); |
| boolean result; |
| |
| 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. |
| * |
| * @return true if the queueing operation succeeded |
| */ |
| public boolean queue(@Nullable ByteBuffer buffer) { |
| // 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 { |
| // 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() { |
| 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(); |
| } |