Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bc4cb0bc authored by Mike Lockwood's avatar Mike Lockwood
Browse files

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: default avatarMike Lockwood <lockwood@android.com>
parent f78b26a5
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -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);
}
+63 −0
Original line number Diff line number Diff line
@@ -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));
}
+44 −0
Original line number Diff line number Diff line
@@ -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 {
+5 −0
Original line number Diff line number Diff line
@@ -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; }
+200 −1
Original line number Diff line number Diff line
@@ -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);
Loading