content: /*
   * 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.mtp;
  
  import android.annotation.NonNull;
  import android.annotation.Nullable;
  import android.content.Context;
  import android.hardware.usb.UsbDevice;
  import android.hardware.usb.UsbDeviceConnection;
  import android.os.CancellationSignal;
  import android.os.ParcelFileDescriptor;
  
  import android.os.UserManager;
  import com.android.internal.annotations.GuardedBy;
  import com.android.internal.util.Preconditions;
  import dalvik.system.CloseGuard;
  
  import java.io.IOException;
  
  /**
   * This class represents an MTP or PTP device connected on the USB host bus. An application can
   * instantiate an object of this type, by referencing an attached {@link
   * android.hardware.usb.UsbDevice} and then use methods in this class to get information about the
   * device and objects stored on it, as well as open the connection and transfer data.
   */
  public final class MtpDevice {
  
      private static final String TAG = "MtpDevice";
  
      private final UsbDevice mDevice;
  
      static {
          System.loadLibrary("media_jni");
      }
  
      /** Make sure that MTP device is closed properly */
      @GuardedBy("mLock")
      private CloseGuard mCloseGuard = CloseGuard.get();
  
      /** Current connection to the {@link #mDevice}, or null if device is not connected */
      @GuardedBy("mLock")
      private UsbDeviceConnection mConnection;
  
      private final Object mLock = new Object();
  
      /**
       * MtpClient constructor
       *
       * @param device the {@link android.hardware.usb.UsbDevice} for the MTP or PTP device
       */
      public MtpDevice(@NonNull UsbDevice device) {
          Preconditions.checkNotNull(device);
          mDevice = device;
      }
  
      /**
       * Opens the MTP device.  Once the device is open it takes ownership of the
       * {@link android.hardware.usb.UsbDeviceConnection}.
       * The connection will be closed when you call {@link #close()}
       * The connection will also be closed if this method fails.
       *
       * @param connection an open {@link android.hardware.usb.UsbDeviceConnection} for the device
       * @return true if the device was successfully opened.
       */
      public boolean open(@NonNull UsbDeviceConnection connection) {
          boolean result = false;
  
          Context context = connection.getContext();
  
          synchronized (mLock) {
              if (context != null) {
                  UserManager userManager = (UserManager) context
                          .getSystemService(Context.USER_SERVICE);
  
                  if (!userManager.hasUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER)) {
                      result = native_open(mDevice.getDeviceName(), connection.getFileDescriptor());
                  }
              }
  
              if (!result) {
                  connection.close();
              } else {
                  mConnection = connection;
                  mCloseGuard.open("close");
              }
          }
  
          return result;
      }
  
      /**
       * Closes all resources related to the MtpDevice object.
       * After this is called, the object can not be used until {@link #open} is called again
       * with a new {@link android.hardware.usb.UsbDeviceConnection}.
       */
      public void close() {
          synchronized (mLock) {
              if (mConnection != null) {
                  mCloseGuard.close();
  
                  native_close();
  
                  mConnection.close();
                  mConnection = null;
              }
          }
      }
  
      @Override
      protected void finalize() throws Throwable {
          try {
              if (mCloseGuard != null) {
                  mCloseGuard.warnIfOpen();
              }
  
              close();
          } finally {
              super.finalize();
          }
      }
  
      /**
       * Returns the name of the USB device
       * This returns the same value as {@link android.hardware.usb.UsbDevice#getDeviceName}
       * for the device's {@link android.hardware.usb.UsbDevice}
       *
       * @return the device name
       */
      public @NonNull String getDeviceName() {
          return mDevice.getDeviceName();
      }
  
      /**
       * Returns the USB ID of the USB device.
       * This returns the same value as {@link android.hardware.usb.UsbDevice#getDeviceId}
       * for the device's {@link android.hardware.usb.UsbDevice}
       *
       * @return the device ID
       */
      public int getDeviceId() {
          return mDevice.getDeviceId();
      }
  
      @Override
      public @NonNull String toString() {
          return mDevice.getDeviceName();
      }
  
      /**
       * Returns the {@link MtpDeviceInfo} for this device
       *
       * @return the device info, or null if fetching device info fails
       */
      public @Nullable MtpDeviceInfo getDeviceInfo() {
          return native_get_device_info();
      }
  
      /**
       * Returns the list of IDs for all storage units on this device
       * Information about each storage unit can be accessed via {@link #getStorageInfo}.
       *
       * @return the list of storage IDs, or null if fetching storage IDs fails
       */
      public @Nullable int[] getStorageIds() {
          return native_get_storage_ids();
      }
  
      /**
       * Returns the list of object handles for all objects on the given storage unit,
       * with the given format and parent.
       * Information about each object can be accessed via {@link #getObjectInfo}.
       *
       * @param storageId the storage unit to query
       * @param format the format of the object to return, or zero for all formats
       * @param objectHandle the parent object to query, -1 for the storage root,
       *     or zero for all objects
       * @return the object handles, or null if fetching object handles fails
       */
      public @Nullable int[] getObjectHandles(int storageId, int format, int objectHandle) {
          return native_get_object_handles(storageId, format, objectHandle);
      }
  
      /**
       * Returns the data for an object as a byte array.
       * This call may block for an arbitrary amount of time depending on the size
       * of the data and speed of the devices.
       *
       * @param objectHandle handle of the object to read
       * @param objectSize the size of the object (this should match
       *      {@link MtpObjectInfo#getCompressedSize})
       * @return the object's data, or null if reading fails
       */
      public @Nullable byte[] getObject(int objectHandle, int objectSize) {
          Preconditions.checkArgumentNonnegative(objectSize, "objectSize should not be negative");
          return native_get_object(objectHandle, objectSize);
      }
  
      /**
       * Obtains object bytes in the specified range and writes it to an array.
       * This call may block for an arbitrary amount of time depending on the size
       * of the data and speed of the devices.
       *
       * @param objectHandle handle of the object to read
       * @param offset Start index of reading range. It must be a non-negative value at most
       *     0xffffffff.
       * @param size Size of reading range. It must be a non-negative value at most Integer.MAX_VALUE
       *     or 0xffffffff. If 0xffffffff is specified, the method obtains the full bytes of object.
       * @param buffer Array to write data.
       * @return Size of bytes that are actually read.
       */
      public long getPartialObject(int objectHandle, long offset, long size, @NonNull byte[] buffer)
              throws IOException {
          return native_get_partial_object(objectHandle, offset, size, buffer);
      }
  
      /**
       * Obtains object bytes in the specified range and writes it to an array.
       * This call may block for an arbitrary amount of time depending on the size
       * of the data and speed of the devices.
       *
       * This is a vender-extended operation supported by Android that enables us to pass
       * unsigned 64-bit offset. Check if the MTP device supports the operation by using
       * {@link MtpDeviceInfo#getOperationsSupported()}.
       *
       * @param objectHandle handle of the object to read
       * @param offset Start index of reading range. It must be a non-negative value.
       * @param size Size of reading range. It must be a non-negative value at most Integer.MAX_VALUE.
       * @param buffer Array to write data.
       * @return Size of bytes that are actually read.
       * @see MtpConstants#OPERATION_GET_PARTIAL_OBJECT_64
       */
      public long getPartialObject64(int objectHandle, long offset, long size, @NonNull byte[] buffer)
              throws IOException {
          return native_get_partial_object_64(objectHandle, offset, size, buffer);
      }
  
      /**
       * Returns the thumbnail data for an object as a byte array.
       * The size and format of the thumbnail data can be determined via
       * {@link MtpObjectInfo#getThumbCompressedSize} and
       * {@link MtpObjectInfo#getThumbFormat}.
       * For typical devices the format is JPEG.
       *
       * @param objectHandle handle of the object to read
       * @return the object's thumbnail, or null if reading fails
       */
      public @Nullable byte[] getThumbnail(int objectHandle) {
          return native_get_thumbnail(objectHandle);
      }
  
      /**
       * Retrieves the {@link MtpStorageInfo} for a storage unit.
       *
       * @param storageId the ID of the storage unit
       * @return the MtpStorageInfo, or null if fetching storage info fails
       */
      public @Nullable MtpStorageInfo getStorageInfo(int storageId) {
          return native_get_storage_info(storageId);
      }
  
      /**
       * Retrieves the {@link MtpObjectInfo} for an object.
       *
       * @param objectHandle the handle of the object
       * @return the MtpObjectInfo, or null if fetching object info fails
       */
      public @Nullable MtpObjectInfo getObjectInfo(int objectHandle) {
          return native_get_object_info(objectHandle);
      }
  
      /**
       * Deletes an object on the device.  This call may block, since
       * deleting a directory containing many files may take a long time
       * on some devices.
       *
       * @param objectHandle handle of the object to delete
       * @return true if the deletion succeeds
       */
      public boolean deleteObject(int objectHandle) {
          return native_delete_object(objectHandle);
      }
  
      /**
       * Retrieves the object handle for the parent of an object on the device.
       *
       * @param objectHandle handle of the object to query
       * @return the parent's handle, or zero if it is in the root of the storage
       */
      public long getParent(int objectHandle) {
          return native_get_parent(objectHandle);
      }
  
      /**
       * Retrieves the ID of the storage unit containing the given object on the device.
       *
       * @param objectHandle handle of the object to query
       * @return the object's storage unit ID
       */
      public long getStorageId(int objectHandle) {
          return native_get_storage_id(objectHandle);
      }
  
      /**
       * Copies the data for an object to a file in external storage.
       * This call may block for an arbitrary amount of time depending on the size
       * of the data and speed of the devices.
       *
       * @param objectHandle handle of the object to read
       * @param destPath path to destination for the file transfer.
       *      This path should be in the external storage as defined by
       *      {@link android.os.Environment#getExternalStorageDirectory}
       * @return true if the file transfer succeeds
       */
      public boolean importFile(int objectHandle, @NonNull String destPath) {
          return native_import_file(objectHandle, destPath);
      }
  
      /**
       * Copies the data for an object to a file descriptor.
       * This call may block for an arbitrary amount of time depending on the size
       * of the data and speed of the devices. The file descriptor is not closed
       * on completion, and must be done by the caller.
       *
       * @param objectHandle handle of the object to read
       * @param descriptor file descriptor to write the data to for the file transfer.
       * @return true if the file transfer succeeds
       */
      public boolean importFile(int objectHandle, @NonNull ParcelFileDescriptor descriptor) {
          return native_import_file(objectHandle, descriptor.getFd());
      }
  
      /**
       * Copies the data for an object from a file descriptor.
       * This call may block for an arbitrary amount of time depending on the size
       * of the data and speed of the devices. The file descriptor is not closed
       * on completion, and must be done by the caller.
       *
       * @param objectHandle handle of the target file
       * @param size size of the file in bytes
       * @param descriptor file descriptor to read the data from.
       * @return true if the file transfer succeeds
       */
      public boolean sendObject(
              int objectHandle, long size, @NonNull ParcelFileDescriptor descriptor) {
          return native_send_object(objectHandle, size, descriptor.getFd());
      }
  
      /**
       * Uploads an object metadata for a new entry. The {@link MtpObjectInfo} can be
       * created with the {@link MtpObjectInfo.Builder} class.
       *
       * The returned {@link MtpObjectInfo} has the new object handle field filled in.
       *
       * @param info metadata of the entry
       * @return object info of the created entry, or null if sending object info fails
       */
      public @Nullable MtpObjectInfo sendObjectInfo(@NonNull MtpObjectInfo info) {
          return native_send_object_info(info);
      }
  
      /**
       * Reads an event from the device. It blocks the current thread until it gets an event.
       * It throws OperationCanceledException if it is cancelled by signal.
       *
       * @param signal signal for cancellation
       * @return obtained event
       * @throws IOException
       */
      public @NonNull MtpEvent readEvent(@Nullable CancellationSignal signal) throws IOException {
          final int handle = native_submit_event_request();
          Preconditions.checkState(handle >= 0, "Other thread is reading an event.");
  
          if (signal != null) {
              signal.setOnCancelListener(new CancellationSignal.OnCancelListener() {
                  @Override
                  public void onCancel() {
                      native_discard_event_request(handle);
                  }
              });
          }
  
          try {
              return native_reap_event_request(handle);
          } finally {
              if (signal != null) {
                  signal.setOnCancelListener(null);
              }
          }
      }
  
      /**
       * Returns object size in 64-bit integer.
       *
       * Though MtpObjectInfo#getCompressedSize returns the object size in 32-bit unsigned integer,
       * this method returns the object size in 64-bit integer from the object property. Thus it can
       * fetch 4GB+ object size correctly. If the device does not support objectSize property, it
       * throws IOException.
       * @hide
       */
      public long getObjectSizeLong(int handle, int format) throws IOException {
          return native_get_object_size_long(handle, format);
      }
  
      // used by the JNI code
      private long mNativeContext;
  
      private native boolean native_open(String deviceName, int fd);
      private native void native_close();
      private native MtpDeviceInfo native_get_device_info();
      private native int[] native_get_storage_ids();
      private native MtpStorageInfo native_get_storage_info(int storageId);
      private native int[] native_get_object_handles(int storageId, int format, int objectHandle);
      private native MtpObjectInfo native_get_object_info(int objectHandle);
      private native byte[] native_get_object(int objectHandle, long objectSize);
      private native long native_get_partial_object(
              int objectHandle, long offset, long objectSize, byte[] buffer) throws IOException;
      private native int native_get_partial_object_64(
              int objectHandle, long offset, long objectSize, byte[] buffer) throws IOException;
      private native byte[] native_get_thumbnail(int objectHandle);
      private native boolean native_delete_object(int objectHandle);
      private native int native_get_parent(int objectHandle);
      private native int native_get_storage_id(int objectHandle);
      private native boolean native_import_file(int objectHandle, String destPath);
      private native boolean native_import_file(int objectHandle, int fd);
      private native boolean native_send_object(int objectHandle, long size, int fd);
      private native MtpObjectInfo native_send_object_info(MtpObjectInfo info);
      private native int native_submit_event_request() throws IOException;
      private native MtpEvent native_reap_event_request(int handle) throws IOException;
      private native void native_discard_event_request(int handle);
      private native long native_get_object_size_long(int handle, int format) throws IOException;
  }
  