content: /*
   * Copyright (C) 2011 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.NonNull;
  import android.annotation.Nullable;
  import android.annotation.SuppressLint;
  import android.annotation.SystemApi;
  import android.annotation.UnsupportedAppUsage;
  import android.content.Context;
  import android.os.Build;
  import android.os.ParcelFileDescriptor;
  
  import com.android.internal.util.Preconditions;
  
  import dalvik.system.CloseGuard;
  
  import java.io.FileDescriptor;
  import java.nio.BufferOverflowException;
  import java.nio.ByteBuffer;
  import java.util.concurrent.TimeoutException;
  
  /**
   * This class is used for sending and receiving data and control messages to a USB device.
   * Instances of this class are created by {@link UsbManager#openDevice}.
   */
  public class UsbDeviceConnection {
  
      private static final String TAG = "UsbDeviceConnection";
  
      private final UsbDevice mDevice;
  
      private Context mContext;
  
      // used by the JNI code
      @UnsupportedAppUsage
      private long mNativeContext;
  
      private final CloseGuard mCloseGuard = CloseGuard.get();
  
      /**
       * UsbDevice should only be instantiated by UsbService implementation
       * @hide
       */
      public UsbDeviceConnection(UsbDevice device) {
          mDevice = device;
      }
  
      /* package */ boolean open(String name, ParcelFileDescriptor pfd, @NonNull Context context) {
          mContext = context.getApplicationContext();
          boolean wasOpened = native_open(name, pfd.getFileDescriptor());
  
          if (wasOpened) {
              mCloseGuard.open("close");
          }
  
          return wasOpened;
      }
  
      /**
       * @return The application context the connection was created for.
       *
       * @hide
       */
      public @Nullable Context getContext() {
          return mContext;
      }
  
      /**
       * Releases all system resources related to the device.
       * Once the object is closed it cannot be used again.
       * The client must call {@link UsbManager#openDevice} again
       * to retrieve a new instance to reestablish communication with the device.
       */
      public void close() {
          if (mNativeContext != 0) {
              native_close();
              mCloseGuard.close();
          }
      }
  
      /**
       * Returns the native file descriptor for the device, or
       * -1 if the device is not opened.
       * This is intended for passing to native code to access the device.
       *
       * @return the native file descriptor
       */
      public int getFileDescriptor() {
          return native_get_fd();
      }
  
      /**
       * Returns the raw USB descriptors for the device.
       * This can be used to access descriptors not supported directly
       * via the higher level APIs.
       *
       * @return raw USB descriptors
       */
      public byte[] getRawDescriptors() {
          return native_get_desc();
      }
  
      /**
       * Claims exclusive access to a {@link android.hardware.usb.UsbInterface}.
       * This must be done before sending or receiving data on any
       * {@link android.hardware.usb.UsbEndpoint}s belonging to the interface.
       *
       * @param intf the interface to claim
       * @param force true to disconnect kernel driver if necessary
       * @return true if the interface was successfully claimed
       */
      public boolean claimInterface(UsbInterface intf, boolean force) {
          return native_claim_interface(intf.getId(), force);
      }
  
      /**
       * Releases exclusive access to a {@link android.hardware.usb.UsbInterface}.
       *
       * @return true if the interface was successfully released
       */
      public boolean releaseInterface(UsbInterface intf) {
          return native_release_interface(intf.getId());
      }
  
      /**
       * Sets the current {@link android.hardware.usb.UsbInterface}.
       * Used to select between two interfaces with the same ID but different alternate setting.
       *
       * @return true if the interface was successfully selected
       */
      public boolean setInterface(UsbInterface intf) {
          return native_set_interface(intf.getId(), intf.getAlternateSetting());
      }
  
      /**
       * Sets the device's current {@link android.hardware.usb.UsbConfiguration}.
       *
       * @return true if the configuration was successfully set
       */
      public boolean setConfiguration(UsbConfiguration configuration) {
          return native_set_configuration(configuration.getId());
      }
  
      /**
       * Performs a control transaction on endpoint zero for this device.
       * The direction of the transfer is determined by the request type.
       * If requestType & {@link UsbConstants#USB_ENDPOINT_DIR_MASK} is
       * {@link UsbConstants#USB_DIR_OUT}, then the transfer is a write,
       * and if it is {@link UsbConstants#USB_DIR_IN}, then the transfer
       * is a read.
       * <p>
       * This method transfers data starting from index 0 in the buffer.
       * To specify a different offset, use
       * {@link #controlTransfer(int, int, int, int, byte[], int, int, int)}.
       * </p>
       *
       * @param requestType request type for this transaction
       * @param request request ID for this transaction
       * @param value value field for this transaction
       * @param index index field for this transaction
       * @param buffer buffer for data portion of transaction,
       * or null if no data needs to be sent or received
       * @param length the length of the data to send or receive
       * @param timeout in milliseconds
       * @return length of data transferred (or zero) for success,
       * or negative value for failure
       */
      public int controlTransfer(int requestType, int request, int value,
              int index, byte[] buffer, int length, int timeout) {
          return controlTransfer(requestType, request, value, index, buffer, 0, length, timeout);
      }
  
      /**
       * Performs a control transaction on endpoint zero for this device.
       * The direction of the transfer is determined by the request type.
       * If requestType & {@link UsbConstants#USB_ENDPOINT_DIR_MASK} is
       * {@link UsbConstants#USB_DIR_OUT}, then the transfer is a write,
       * and if it is {@link UsbConstants#USB_DIR_IN}, then the transfer
       * is a read.
       *
       * @param requestType request type for this transaction
       * @param request request ID for this transaction
       * @param value value field for this transaction
       * @param index index field for this transaction
       * @param buffer buffer for data portion of transaction,
       * or null if no data needs to be sent or received
       * @param offset the index of the first byte in the buffer to send or receive
       * @param length the length of the data to send or receive
       * @param timeout in milliseconds
       * @return length of data transferred (or zero) for success,
       * or negative value for failure
       */
      public int controlTransfer(int requestType, int request, int value, int index,
              byte[] buffer, int offset, int length, int timeout) {
          checkBounds(buffer, offset, length);
          return native_control_request(requestType, request, value, index,
                  buffer, offset, length, timeout);
      }
  
      /**
       * Performs a bulk transaction on the given endpoint.
       * The direction of the transfer is determined by the direction of the endpoint.
       * <p>
       * This method transfers data starting from index 0 in the buffer.
       * To specify a different offset, use
       * {@link #bulkTransfer(UsbEndpoint, byte[], int, int, int)}.
       * </p>
       *
       * @param endpoint the endpoint for this transaction
       * @param buffer buffer for data to send or receive; can be {@code null} to wait for next
       *               transaction without reading data
       * @param length the length of the data to send or receive. 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.
       * @param timeout in milliseconds, 0 is infinite
       * @return length of data transferred (or zero) for success,
       * or negative value for failure
       */
      public int bulkTransfer(UsbEndpoint endpoint,
              byte[] buffer, int length, int timeout) {
          return bulkTransfer(endpoint, buffer, 0, length, timeout);
      }
  
      /**
       * Performs a bulk transaction on the given endpoint.
       * The direction of the transfer is determined by the direction of the endpoint.
       *
       * @param endpoint the endpoint for this transaction
       * @param buffer buffer for data to send or receive
       * @param offset the index of the first byte in the buffer to send or receive
       * @param length the length of the data to send or receive. 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.
       * @param timeout in milliseconds, 0 is infinite
       * @return length of data transferred (or zero) for success,
       * or negative value for failure
       */
      public int bulkTransfer(UsbEndpoint endpoint,
              byte[] buffer, int offset, int length, int timeout) {
          checkBounds(buffer, offset, length);
          if (mContext.getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P
                  && length > UsbRequest.MAX_USBFS_BUFFER_SIZE) {
              length = UsbRequest.MAX_USBFS_BUFFER_SIZE;
          }
          return native_bulk_request(endpoint.getAddress(), buffer, offset, length, timeout);
      }
  
      /**
       * Reset USB port for the connected device.
       *
       * @return true if reset succeeds.
       *
       * @hide
       */
      @SystemApi
      @SuppressLint("Doclava125")
      public boolean resetDevice() {
          return native_reset_device();
      }
  
      /**
       * Waits for the result of a {@link android.hardware.usb.UsbRequest#queue} operation
       * <p>Note that this may return requests queued on multiple
       * {@link android.hardware.usb.UsbEndpoint}s. When multiple endpoints are in use,
       * {@link android.hardware.usb.UsbRequest#getEndpoint} and {@link
       * android.hardware.usb.UsbRequest#getClientData} can be useful in determining how to process
       * the result of this function.</p>
       *
       * @return a completed USB request, or null if an error occurred
       *
       * @throws IllegalArgumentException Before API {@value Build.VERSION_CODES#O}: if the number of
       *                                  bytes read or written is more than the limit of the
       *                                  request's buffer. The number of bytes is determined by the
       *                                  {@code length} parameter of
       *                                  {@link UsbRequest#queue(ByteBuffer, int)}
       * @throws BufferOverflowException In API {@value Build.VERSION_CODES#O} and after: if the
       *                                 number of bytes read or written is more than the limit of the
       *                                 request's buffer. The number of bytes is determined by the
       *                                 {@code length} parameter of
       *                                 {@link UsbRequest#queue(ByteBuffer, int)}
       */
      public UsbRequest requestWait() {
          UsbRequest request = null;
          try {
              // -1 is special value indicating infinite wait
              request = native_request_wait(-1);
          } catch (TimeoutException e) {
              // Does not happen, infinite timeout
          }
  
          if (request != null) {
              request.dequeue(
                      mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O);
          }
          return request;
      }
  
      /**
       * Waits for the result of a {@link android.hardware.usb.UsbRequest#queue} operation
       * <p>Note that this may return requests queued on multiple
       * {@link android.hardware.usb.UsbEndpoint}s. When multiple endpoints are in use,
       * {@link android.hardware.usb.UsbRequest#getEndpoint} and {@link
       * android.hardware.usb.UsbRequest#getClientData} can be useful in determining how to process
       * the result of this function.</p>
       * <p>Android processes {@link UsbRequest UsbRequests} asynchronously. Hence it is not
       * guaranteed that {@link #requestWait(long) requestWait(0)} returns a request that has been
       * queued right before even if the request could have been processed immediately.</p>
       *
       * @param timeout timeout in milliseconds. If 0 this method does not wait.
       *
       * @return a completed USB request, or {@code null} if an error occurred
       *
       * @throws BufferOverflowException if the number of bytes read or written is more than the
       *                                 limit of the request's buffer. The number of bytes is
       *                                 determined by the {@code length} parameter of
       *                                 {@link UsbRequest#queue(ByteBuffer, int)}
       * @throws TimeoutException if no request was received in {@code timeout} milliseconds.
       */
      public UsbRequest requestWait(long timeout) throws TimeoutException {
          timeout = Preconditions.checkArgumentNonnegative(timeout, "timeout");
  
          UsbRequest request = native_request_wait(timeout);
          if (request != null) {
              request.dequeue(true);
          }
          return request;
      }
  
      /**
       * Returns the serial number for the device.
       * This will return null if the device has not been opened.
       *
       * @return the device serial number
       */
      public String getSerial() {
          return native_get_serial();
      }
  
      private static void checkBounds(byte[] buffer, int start, int length) {
          final int bufferLength = (buffer != null ? buffer.length : 0);
          if (length < 0 || start < 0 || start + length > bufferLength) {
              throw new IllegalArgumentException("Buffer start or length out of bounds.");
          }
      }
  
      @Override
      protected void finalize() throws Throwable {
          try {
              if (mCloseGuard != null) {
                  mCloseGuard.warnIfOpen();
              }
          } finally {
              super.finalize();
          }
      }
  
      private native boolean native_open(String deviceName, FileDescriptor pfd);
      private native void native_close();
      private native int native_get_fd();
      private native byte[] native_get_desc();
      private native boolean native_claim_interface(int interfaceID, boolean force);
      private native boolean native_release_interface(int interfaceID);
      private native boolean native_set_interface(int interfaceID, int alternateSetting);
      private native boolean native_set_configuration(int configurationID);
      private native int native_control_request(int requestType, int request, int value,
              int index, byte[] buffer, int offset, int length, int timeout);
      private native int native_bulk_request(int endpoint, byte[] buffer,
              int offset, int length, int timeout);
      private native UsbRequest native_request_wait(long timeout) throws TimeoutException;
      private native String native_get_serial();
      private native boolean native_reset_device();
  }
  