Merge "MTP host: Add support for reading files from an MTP device via ParcelFileDescriptor"
diff --git a/media/java/android/media/MtpClient.java b/media/java/android/media/MtpClient.java
index 0fe9bb4..1aebcb8 100644
--- a/media/java/android/media/MtpClient.java
+++ b/media/java/android/media/MtpClient.java
@@ -16,6 +16,7 @@
 
 package android.media;
 
+import android.os.ParcelFileDescriptor;
 import android.util.Log;
 
 /**
@@ -64,6 +65,11 @@
         return native_get_storage_id(deviceID, objectID);
     }
 
+    // create a file descriptor for reading the contents of an object over MTP
+    public ParcelFileDescriptor openFile(int deviceID, int objectID) {
+        return native_open_file(deviceID, objectID);
+    }
+
     public interface Listener {
         // called when a new MTP device has been discovered
         void deviceAdded(int id);
@@ -94,4 +100,5 @@
     private native boolean native_delete_object(int deviceID, int objectID);
     private native int native_get_parent(int deviceID, int objectID);
     private native int native_get_storage_id(int deviceID, int objectID);
+    private native ParcelFileDescriptor native_open_file(int deviceID, int objectID);
 }
diff --git a/media/jni/android_media_MtpClient.cpp b/media/jni/android_media_MtpClient.cpp
index f69053c..67740bc 100644
--- a/media/jni/android_media_MtpClient.cpp
+++ b/media/jni/android_media_MtpClient.cpp
@@ -29,6 +29,7 @@
 
 #include "MtpClient.h"
 #include "MtpDevice.h"
+#include "MtpObjectInfo.h"
 
 using namespace android;
 
@@ -38,6 +39,19 @@
 static jmethodID method_deviceRemoved;
 static jfieldID field_context;
 
+static struct file_descriptor_offsets_t
+{
+    jclass mClass;
+    jmethodID mConstructor;
+    jfieldID mDescriptor;
+} gFileDescriptorOffsets;
+
+static struct parcel_file_descriptor_offsets_t
+{
+    jclass mClass;
+    jmethodID mConstructor;
+} gParcelFileDescriptorOffsets;
+
 #ifdef HAVE_ANDROID_OS
 
 static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
@@ -187,6 +201,38 @@
         return -1;
 }
 
+static jobject
+android_media_MtpClient_open_file(JNIEnv *env, jobject thiz,
+        jint device_id, jint object_id)
+{
+#ifdef HAVE_ANDROID_OS
+    MyClient *client = (MyClient *)env->GetIntField(thiz, field_context);
+    MtpDevice* device = client->getDevice(device_id);
+    if (!device)
+        return NULL;
+
+    MtpObjectInfo* info = device->getObjectInfo(object_id);
+    if (!info)
+        return NULL;
+    int object_size = info->mCompressedSize;
+    delete info;
+    int fd = device->readObject(object_id, object_size);
+    if (fd < 0)
+        return NULL;
+
+    jobject fileDescriptor = env->NewObject(gFileDescriptorOffsets.mClass,
+        gFileDescriptorOffsets.mConstructor);
+    if (fileDescriptor != NULL) {
+        env->SetIntField(fileDescriptor, gFileDescriptorOffsets.mDescriptor, fd);
+    } else {
+        return NULL;
+    }
+    return env->NewObject(gParcelFileDescriptorOffsets.mClass,
+        gParcelFileDescriptorOffsets.mConstructor, fileDescriptor);
+#endif
+    return NULL;
+}
+
 // ----------------------------------------------------------------------------
 
 static JNINativeMethod gMethods[] = {
@@ -197,6 +243,8 @@
     {"native_delete_object",   "(II)Z", (void *)android_media_MtpClient_delete_object},
     {"native_get_parent",      "(II)I", (void *)android_media_MtpClient_get_parent},
     {"native_get_storage_id",  "(II)I", (void *)android_media_MtpClient_get_storage_id},
+    {"native_open_file",       "(II)Landroid/os/ParcelFileDescriptor;",
+                                        (void *)android_media_MtpClient_open_file},
 };
 
 static const char* const kClassPathName = "android/media/MtpClient";
@@ -228,6 +276,21 @@
         return -1;
     }
 
+   clazz = env->FindClass("java/io/FileDescriptor");
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class java.io.FileDescriptor");
+    gFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
+    gFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "()V");
+    gFileDescriptorOffsets.mDescriptor = env->GetFieldID(clazz, "descriptor", "I");
+    LOG_FATAL_IF(gFileDescriptorOffsets.mDescriptor == NULL,
+                 "Unable to find descriptor field in java.io.FileDescriptor");
+
+   clazz = env->FindClass("android/os/ParcelFileDescriptor");
+    LOG_FATAL_IF(clazz == NULL, "Unable to find class android.os.ParcelFileDescriptor");
+    gParcelFileDescriptorOffsets.mClass = (jclass) env->NewGlobalRef(clazz);
+    gParcelFileDescriptorOffsets.mConstructor = env->GetMethodID(clazz, "<init>", "(Ljava/io/FileDescriptor;)V");
+    LOG_FATAL_IF(gParcelFileDescriptorOffsets.mConstructor == NULL,
+                 "Unable to find constructor for android.os.ParcelFileDescriptor");
+
     return AndroidRuntime::registerNativeMethods(env,
                 "android/media/MtpClient", gMethods, NELEM(gMethods));
 }
diff --git a/media/mtp/MtpDataPacket.cpp b/media/mtp/MtpDataPacket.cpp
index ebe764a..c159e20 100644
--- a/media/mtp/MtpDataPacket.cpp
+++ b/media/mtp/MtpDataPacket.cpp
@@ -20,6 +20,8 @@
 #include <sys/types.h>
 #include <fcntl.h>
 
+#include <usbhost/usbhost.h>
+
 #include "MtpDataPacket.h"
 #include "MtpStringBuffer.h"
 
@@ -391,6 +393,35 @@
     return length;
 }
 
+int MtpDataPacket::readData(struct usb_endpoint *ep, void* buffer, int length) {
+    int packetSize = usb_endpoint_max_packet(ep);
+    int read = 0;
+    while (read < length) {
+        int ret = transfer(ep, (char *)buffer + read, packetSize);
+        if (ret < 0) {
+printf("MtpDataPacket::readData returning %d\n", ret);
+            return ret;
+        }
+        read += ret;
+    }
+printf("MtpDataPacket::readData returning %d\n", read);
+    return read;
+}
+
+int MtpDataPacket::readDataHeader(struct usb_endpoint *ep) {
+    int length = transfer(ep, mBuffer, usb_endpoint_max_packet(ep));
+    if (length >= 0)
+        mPacketSize = length;
+    return length;
+}
+
+int MtpDataPacket::writeDataHeader(struct usb_endpoint *ep, uint32_t length) {
+    MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, length);
+    MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA);
+    int ret = transfer(ep, mBuffer, MTP_CONTAINER_HEADER_SIZE);
+    return (ret < 0 ? ret : 0);
+}
+
 int MtpDataPacket::write(struct usb_endpoint *ep) {
     MtpPacket::putUInt32(MTP_CONTAINER_LENGTH_OFFSET, mPacketSize);
     MtpPacket::putUInt16(MTP_CONTAINER_TYPE_OFFSET, MTP_CONTAINER_TYPE_DATA);
@@ -403,6 +434,19 @@
     return (ret < 0 ? ret : 0);
 }
 
+int MtpDataPacket::write(struct usb_endpoint *ep, void* buffer, uint32_t length) {
+    int ret = 0;
+    int packetSize = usb_endpoint_max_packet(ep);
+    while (length > 0) {
+        int write = (length > packetSize ? packetSize : length);
+        int ret = transfer(ep, buffer, write);
+        if (ret < 0)
+            break;
+        length -= ret;
+    }
+    return (ret < 0 ? ret : 0);
+}
+
 #endif // MTP_HOST
 
 void* MtpDataPacket::getData(int& outLength) const {
diff --git a/media/mtp/MtpDataPacket.h b/media/mtp/MtpDataPacket.h
index 9a24d61..e8314d7 100644
--- a/media/mtp/MtpDataPacket.h
+++ b/media/mtp/MtpDataPacket.h
@@ -98,7 +98,12 @@
 
 #ifdef MTP_HOST
     int                 read(struct usb_endpoint *ep);
+    int                 readData(struct usb_endpoint *ep, void* buffer, int length);
+    int                 readDataHeader(struct usb_endpoint *ep);
+
+    int                 writeDataHeader(struct usb_endpoint *ep, uint32_t length);
     int                 write(struct usb_endpoint *ep);
+    int                 write(struct usb_endpoint *ep, void* buffer, uint32_t length);
 #endif
 
     inline bool         hasData() const { return mPacketSize > MTP_CONTAINER_HEADER_SIZE; }
diff --git a/media/mtp/MtpDevice.cpp b/media/mtp/MtpDevice.cpp
index 3ceb9b4..367694b 100644
--- a/media/mtp/MtpDevice.cpp
+++ b/media/mtp/MtpDevice.cpp
@@ -23,6 +23,7 @@
 #include "MtpProperty.h"
 #include "MtpStorageInfo.h"
 #include "MtpStringBuffer.h"
+#include "MtpUtils.h"
 
 #include <stdio.h>
 #include <stdlib.h>
@@ -31,6 +32,7 @@
 #include <sys/stat.h>
 #include <fcntl.h>
 #include <errno.h>
+#include <endian.h>
 
 #include <usbhost/usbhost.h>
 
@@ -93,6 +95,8 @@
 }
 
 bool MtpDevice::openSession() {
+    Mutex::Autolock autoLock(mMutex);
+
     mSessionID = 0;
     mTransactionID = 0;
     MtpSessionID newSession = 1;
@@ -117,6 +121,8 @@
 }
 
 MtpDeviceInfo* MtpDevice::getDeviceInfo() {
+    Mutex::Autolock autoLock(mMutex);
+
     mRequest.reset();
     if (!sendRequest(MTP_OPERATION_GET_DEVICE_INFO))
         return NULL;
@@ -132,6 +138,8 @@
 }
 
 MtpStorageIDList* MtpDevice::getStorageIDs() {
+    Mutex::Autolock autoLock(mMutex);
+
     mRequest.reset();
     if (!sendRequest(MTP_OPERATION_GET_STORAGE_IDS))
         return NULL;
@@ -145,6 +153,8 @@
 }
 
 MtpStorageInfo* MtpDevice::getStorageInfo(MtpStorageID storageID) {
+    Mutex::Autolock autoLock(mMutex);
+
     mRequest.reset();
     mRequest.setParameter(1, storageID);
     if (!sendRequest(MTP_OPERATION_GET_STORAGE_INFO))
@@ -162,6 +172,8 @@
 
 MtpObjectHandleList* MtpDevice::getObjectHandles(MtpStorageID storageID,
             MtpObjectFormat format, MtpObjectHandle parent) {
+    Mutex::Autolock autoLock(mMutex);
+
     mRequest.reset();
     mRequest.setParameter(1, storageID);
     mRequest.setParameter(2, format);
@@ -178,6 +190,8 @@
 }
 
 MtpObjectInfo* MtpDevice::getObjectInfo(MtpObjectHandle handle) {
+    Mutex::Autolock autoLock(mMutex);
+
     // FIXME - we might want to add some caching here
 
     mRequest.reset();
@@ -196,6 +210,8 @@
 }
 
 void* MtpDevice::getThumbnail(MtpObjectHandle handle, int& outLength) {
+    Mutex::Autolock autoLock(mMutex);
+
     mRequest.reset();
     mRequest.setParameter(1, handle);
     if (sendRequest(MTP_OPERATION_GET_THUMB) && readData()) {
@@ -208,7 +224,90 @@
     return NULL;
 }
 
+MtpObjectHandle MtpDevice::sendObjectInfo(MtpObjectInfo* info) {
+    Mutex::Autolock autoLock(mMutex);
+
+    mRequest.reset();
+    MtpObjectHandle parent = info->mParent;
+    if (parent == 0)
+        parent = MTP_PARENT_ROOT;
+
+    mRequest.setParameter(1, info->mStorageID);
+    mRequest.setParameter(2, info->mParent);
+
+    mData.putUInt32(info->mStorageID);
+    mData.putUInt16(info->mFormat);
+    mData.putUInt16(info->mProtectionStatus);
+    mData.putUInt32(info->mCompressedSize);
+    mData.putUInt16(info->mThumbFormat);
+    mData.putUInt32(info->mThumbCompressedSize);
+    mData.putUInt32(info->mThumbPixWidth);
+    mData.putUInt32(info->mThumbPixHeight);
+    mData.putUInt32(info->mImagePixWidth);
+    mData.putUInt32(info->mImagePixHeight);
+    mData.putUInt32(info->mImagePixDepth);
+    mData.putUInt32(info->mParent);
+    mData.putUInt16(info->mAssociationType);
+    mData.putUInt32(info->mAssociationDesc);
+    mData.putUInt32(info->mSequenceNumber);
+    mData.putString(info->mName);
+
+    char created[100], modified[100];
+    formatDateTime(info->mDateCreated, created, sizeof(created));
+    formatDateTime(info->mDateModified, modified, sizeof(modified));
+
+    mData.putString(created);
+    mData.putString(modified);
+    if (info->mKeywords)
+        mData.putString(info->mKeywords);
+    else
+        mData.putEmptyString();
+
+   if (sendRequest(MTP_OPERATION_SEND_OBJECT_INFO) && sendData()) {
+        printf("MTP_OPERATION_SEND_OBJECT_INFO sent\n");
+        MtpResponseCode ret = readResponse();
+        printf("sendObjectInfo response: %04X\n", ret);
+        if (ret == MTP_RESPONSE_OK) {
+            info->mStorageID = mResponse.getParameter(1);
+            info->mParent = mResponse.getParameter(2);
+            info->mHandle = mResponse.getParameter(3);
+            return info->mHandle;
+        }
+    }
+    return (MtpObjectHandle)-1;
+}
+
+bool MtpDevice::sendObject(MtpObjectInfo* info, int srcFD) {
+    Mutex::Autolock autoLock(mMutex);
+
+    int remaining = info->mCompressedSize;
+    mRequest.reset();
+    mRequest.setParameter(1, info->mHandle);
+    if (sendRequest(MTP_OPERATION_SEND_OBJECT)) {
+        printf("MTP_OPERATION_SEND_OBJECT sent\n");
+        // send data header
+        writeDataHeader(MTP_OPERATION_SEND_OBJECT, remaining);
+
+        char buffer[65536];
+        while (remaining > 0) {
+            int count = read(srcFD, buffer, sizeof(buffer));
+            if (count > 0) {
+                int written = mData.write(mEndpointOut, buffer, count);
+                printf("wrote %d\n", written);
+                // FIXME check error
+                remaining -= count;
+            } else {
+                break;
+            }
+        }
+    }
+    MtpResponseCode ret = readResponse();
+    return (remaining == 0 && ret == MTP_RESPONSE_OK);
+}
+
 bool MtpDevice::deleteObject(MtpObjectHandle handle) {
+    Mutex::Autolock autoLock(mMutex);
+
     mRequest.reset();
     mRequest.setParameter(1, handle);
     if (sendRequest(MTP_OPERATION_DELETE_OBJECT)) {
@@ -236,6 +335,8 @@
 }
 
 MtpProperty* MtpDevice::getDevicePropDesc(MtpDeviceProperty code) {
+    Mutex::Autolock autoLock(mMutex);
+
     mRequest.reset();
     mRequest.setParameter(1, code);
     if (!sendRequest(MTP_OPERATION_GET_DEVICE_PROP_DESC))
@@ -251,6 +352,98 @@
     return NULL;
 }
 
+class ReadObjectThread : public Thread {
+private:
+    MtpDevice*          mDevice;
+    MtpObjectHandle     mHandle;
+    int                 mObjectSize;
+    void*               mInitialData;
+    int                 mInitialDataLength;
+    int                 mFD;
+
+public:
+    ReadObjectThread(MtpDevice* device, MtpObjectHandle handle, int objectSize)
+        : mDevice(device),
+          mHandle(handle),
+          mObjectSize(objectSize),
+          mInitialData(NULL),
+          mInitialDataLength(0)
+    {
+    }
+
+    virtual ~ReadObjectThread() {
+        if (mFD >= 0)
+            close(mFD);
+        free(mInitialData);
+    }
+
+    // returns file descriptor
+    int init() {
+        mDevice->mRequest.reset();
+        mDevice->mRequest.setParameter(1, mHandle);
+        if (mDevice->sendRequest(MTP_OPERATION_GET_OBJECT)
+                && mDevice->mData.readDataHeader(mDevice->mEndpointIn)) {
+
+            // mData will contain header and possibly the beginning of the object data
+            mInitialData = mDevice->mData.getData(mInitialDataLength);
+
+            // create a pipe for the client to read from
+            int pipefd[2];
+            if (pipe(pipefd) < 0) {
+                LOGE("pipe failed (%s)", strerror(errno));
+                return -1;
+            }
+
+            mFD = pipefd[1];
+            return pipefd[0];
+        } else {
+           return -1;
+        }
+    }
+
+    virtual bool threadLoop() {
+        int remaining = mObjectSize;
+        if (mInitialData) {
+            write(mFD, mInitialData, mInitialDataLength);
+            remaining -= mInitialDataLength;
+            free(mInitialData);
+            mInitialData = NULL;
+        }
+
+        char buffer[65536];
+        while (remaining > 0) {
+            int readSize = (remaining > sizeof(buffer) ? sizeof(buffer) : remaining);
+            int count = mDevice->mData.readData(mDevice->mEndpointIn, buffer, readSize);
+            int written;
+            if (count >= 0) {
+                int written = write(mFD, buffer, count);
+                // FIXME check error
+                remaining -= count;
+            } else {
+                break;
+            }
+        }
+
+        MtpResponseCode ret = mDevice->readResponse();
+        mDevice->mMutex.unlock();
+        return false;
+    }
+};
+
+    // returns the file descriptor for a pipe to read the object's data
+int MtpDevice::readObject(MtpObjectHandle handle, int objectSize) {
+    mMutex.lock();
+
+    ReadObjectThread* thread = new ReadObjectThread(this, handle, objectSize);
+    int fd = thread->init();
+    if (fd < 0) {
+        delete thread;
+        mMutex.unlock();
+    } else {
+        thread->run("ReadObjectThread");
+    }
+    return fd;
+}
 
 bool MtpDevice::sendRequest(MtpOperationCode operation) {
     LOGD("sendRequest: %s\n", MtpDebug::getOperationCodeName(operation));
@@ -262,7 +455,7 @@
     return (ret > 0);
 }
 
-bool MtpDevice::sendData(MtpOperationCode operation) {
+bool MtpDevice::sendData() {
     LOGD("sendData\n");
     mData.setOperationCode(mRequest.getOperationCode());
     mData.setTransactionID(mRequest.getTransactionID());
@@ -285,6 +478,12 @@
     }
 }
 
+bool MtpDevice::writeDataHeader(MtpOperationCode operation, int dataLength) {
+    mData.setOperationCode(operation);
+    mData.setTransactionID(mRequest.getTransactionID());
+    return (!mData.writeDataHeader(mEndpointOut, dataLength));
+}
+
 MtpResponseCode MtpDevice::readResponse() {
     LOGD("readResponse\n");
     int ret = mResponse.read(mEndpointIn);
diff --git a/media/mtp/MtpDevice.h b/media/mtp/MtpDevice.h
index e41a872..57f492f 100644
--- a/media/mtp/MtpDevice.h
+++ b/media/mtp/MtpDevice.h
@@ -22,6 +22,8 @@
 #include "MtpResponsePacket.h"
 #include "MtpTypes.h"
 
+#include <utils/threads.h>
+
 struct usb_device;
 
 namespace android {
@@ -52,6 +54,9 @@
     MtpDataPacket           mData;
     MtpResponsePacket       mResponse;
 
+    // to ensure only one MTP transaction at a time
+    Mutex                   mMutex;
+
 public:
                             MtpDevice(struct usb_device* device, int interface,
                                     struct usb_endpoint *ep_in, struct usb_endpoint *ep_out,
@@ -73,16 +78,24 @@
     MtpObjectHandleList*    getObjectHandles(MtpStorageID storageID, MtpObjectFormat format, MtpObjectHandle parent);
     MtpObjectInfo*          getObjectInfo(MtpObjectHandle handle);
     void*                   getThumbnail(MtpObjectHandle handle, int& outLength);
+    MtpObjectHandle         sendObjectInfo(MtpObjectInfo* info);
+    bool                    sendObject(MtpObjectInfo* info, int srcFD);
     bool                    deleteObject(MtpObjectHandle handle);
     MtpObjectHandle         getParent(MtpObjectHandle handle);
     MtpObjectHandle         getStorageID(MtpObjectHandle handle);
 
     MtpProperty*            getDevicePropDesc(MtpDeviceProperty code);
 
+    // returns the file descriptor for a pipe to read the object's data
+    int                     readObject(MtpObjectHandle handle, int objectSize);
+
 private:
+    friend class ReadObjectThread;
+
     bool                    sendRequest(MtpOperationCode operation);
-    bool                    sendData(MtpOperationCode operation);
+    bool                    sendData();
     bool                    readData();
+    bool                    writeDataHeader(MtpOperationCode operation, int dataLength);
     MtpResponseCode         readResponse();
 
 };