summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMike Lockwood <lockwood@android.com>2010-07-26 20:40:45 -0400
committerMike Lockwood <lockwood@android.com>2010-07-26 20:40:45 -0400
commitbc4cb0bc79c0d366d6866fe4054a71e00d169ff9 (patch)
tree575e35759dcc1ae6a6e6efab1960d57d73f7a426
parentf78b26a572e3ba5d47009f3a810a15e252b5503c (diff)
downloadframeworks_base-bc4cb0bc79c0d366d6866fe4054a71e00d169ff9.zip
frameworks_base-bc4cb0bc79c0d366d6866fe4054a71e00d169ff9.tar.gz
frameworks_base-bc4cb0bc79c0d366d6866fe4054a71e00d169ff9.tar.bz2
MTP host: Add support for reading files from an MTP device via ParcelFileDescriptor
Also added some support for sending files to the device that hasn't been debugged yet. Add locking to MtpDevice to prevent it from attempting multiple transactions simultaneously. Change-Id: I2b995ba0af086cc6920bd6b8c869f540ad78560a Signed-off-by: Mike Lockwood <lockwood@android.com>
-rw-r--r--media/java/android/media/MtpClient.java7
-rw-r--r--media/jni/android_media_MtpClient.cpp63
-rw-r--r--media/mtp/MtpDataPacket.cpp44
-rw-r--r--media/mtp/MtpDataPacket.h5
-rw-r--r--media/mtp/MtpDevice.cpp201
-rw-r--r--media/mtp/MtpDevice.h15
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();
};