diff options
-rw-r--r-- | media/java/android/media/MtpClient.java | 7 | ||||
-rw-r--r-- | media/jni/android_media_MtpClient.cpp | 63 | ||||
-rw-r--r-- | media/mtp/MtpDataPacket.cpp | 44 | ||||
-rw-r--r-- | media/mtp/MtpDataPacket.h | 5 | ||||
-rw-r--r-- | media/mtp/MtpDevice.cpp | 201 | ||||
-rw-r--r-- | media/mtp/MtpDevice.h | 15 |
6 files changed, 333 insertions, 2 deletions
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 @@ public class MtpClient { 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 @@ public class MtpClient { 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_deviceAdded; 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 @@ android_media_MtpClient_get_storage_id(JNIEnv *env, jobject thiz, 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 @@ static JNINativeMethod gMethods[] = { {"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 @@ int register_android_media_MtpClient(JNIEnv *env) 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 @@ int MtpDataPacket::read(struct usb_endpoint *ep) { 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 @@ int MtpDataPacket::write(struct usb_endpoint *ep) { 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 @@ public: #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 @@ const char* MtpDevice::getDeviceName() { } bool MtpDevice::openSession() { + Mutex::Autolock autoLock(mMutex); + mSessionID = 0; mTransactionID = 0; MtpSessionID newSession = 1; @@ -117,6 +121,8 @@ bool MtpDevice::closeSession() { } MtpDeviceInfo* MtpDevice::getDeviceInfo() { + Mutex::Autolock autoLock(mMutex); + mRequest.reset(); if (!sendRequest(MTP_OPERATION_GET_DEVICE_INFO)) return NULL; @@ -132,6 +138,8 @@ MtpDeviceInfo* MtpDevice::getDeviceInfo() { } MtpStorageIDList* MtpDevice::getStorageIDs() { + Mutex::Autolock autoLock(mMutex); + mRequest.reset(); if (!sendRequest(MTP_OPERATION_GET_STORAGE_IDS)) return NULL; @@ -145,6 +153,8 @@ MtpStorageIDList* MtpDevice::getStorageIDs() { } 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 @@ MtpStorageInfo* MtpDevice::getStorageInfo(MtpStorageID storageID) { 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 @@ MtpObjectHandleList* MtpDevice::getObjectHandles(MtpStorageID storageID, } MtpObjectInfo* MtpDevice::getObjectInfo(MtpObjectHandle handle) { + Mutex::Autolock autoLock(mMutex); + // FIXME - we might want to add some caching here mRequest.reset(); @@ -196,6 +210,8 @@ MtpObjectInfo* MtpDevice::getObjectInfo(MtpObjectHandle handle) { } 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 @@ void* MtpDevice::getThumbnail(MtpObjectHandle handle, int& outLength) { 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 @@ MtpObjectHandle MtpDevice::getStorageID(MtpObjectHandle handle) { } 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 @@ MtpProperty* MtpDevice::getDevicePropDesc(MtpDeviceProperty code) { 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 @@ bool MtpDevice::sendRequest(MtpOperationCode operation) { 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::readData() { } } +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 @@ private: 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 @@ public: 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(); }; |