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

Commit 2f493f0f authored by Mike Lockwood's avatar Mike Lockwood Committed by Android (Google) Code Review
Browse files

Merge "MTP host: Add support for reading files from an MTP device via ParcelFileDescriptor"

parents f7785521 bc4cb0bc
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