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

Commit 954c2677 authored by Mike Lockwood's avatar Mike Lockwood
Browse files

PTP: Improve performance and reliability of file importing



Now the file copy is done completely within the media process
rather than pushing data to the client via ContProvider.openFile().

File system writes are now interleaved with USB reads, which allows us
to copy the data faster and prevents the camera from timing out during transfer.

File is automatically inserted in the media provider after a successful import
and a Uri is returned to the client.

BUG: 2994234

Change-Id: Ie75c63da76f623343d3d966c6a707aa1ae871972
Signed-off-by: default avatarMike Lockwood <lockwood@android.com>
parent a8bbc11a
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -112,6 +112,25 @@ public final class Mtp
                    + "/storage/" + storageID + "/child");
        }

        /**
         * Used for copying files from device to host.
         * Constructs a Uri based on the ID of the device and object for the source file,
         * and the path for the destination file.
         * When passed to the ContentProvider.insert() method, the file will be transferred
         * to the specified destination directory and insert() will return a content Uri
         * for the new file in the MediaProvider.
         * ContentProvider.insert() will throw IllegalArgumentException if the destination
         * path is not in the external storage or internal media directory.
         */
        public static Uri getContentUriForImport(int deviceID, long objectID, String destPath) {
            if (destPath.length() == 0 || destPath.charAt(0) != '/') {
                throw new IllegalArgumentException(
                        "destPath must be a full path in getContentUriForImport");
            }
            return Uri.parse(CONTENT_AUTHORITY_DEVICE_SLASH + deviceID
                    + "/import/" + objectID + "?" +  destPath);
        }

        /**
         * The following columns correspond to the fields in the ObjectInfo dataset
         * as described in the MTP specification.
+5 −5
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package android.media;

import android.os.ParcelFileDescriptor;
import android.util.Log;

/**
@@ -69,9 +68,10 @@ 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, long objectID) {
        return native_open_file(deviceID, objectID);
    // Reads a file from device to host to the specified destination.
    // Returns true if the transfer succeeds.
    public boolean importFile(int deviceID, long objectID, String destPath) {
        return native_import_file(deviceID, objectID, destPath);
    }

    public interface Listener {
@@ -104,5 +104,5 @@ public class MtpClient {
    private native boolean native_delete_object(int deviceID, long objectID);
    private native long native_get_parent(int deviceID, long objectID);
    private native long native_get_storage_id(int deviceID, long objectID);
    private native ParcelFileDescriptor native_open_file(int deviceID, long objectID);
    private native boolean native_import_file(int deviceID, long objectID, String destPath);
}
+1 −0
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ public final class MtpCursor extends AbstractWindowedCursor {
    public static final int OBJECT_ID           = 6;
    public static final int STORAGE_CHILDREN    = 7;
    public static final int OBJECT_CHILDREN     = 8;
    public static final int OBJECT_IMPORT       = 9;

    /** The names of the columns in the projection */
    private String[] mColumns;
+10 −53
Original line number Diff line number Diff line
@@ -39,19 +39,6 @@ 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) {
@@ -201,34 +188,19 @@ 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, jlong object_id)
static jboolean
android_media_MtpClient_import_file(JNIEnv *env, jobject thiz,
        jint device_id, jlong object_id, jstring dest_path)
{
#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;
    if (device) {
        const char *destPathStr = env->GetStringUTFChars(dest_path, NULL);
        bool result = device->readObject(object_id, destPathStr);
        env->ReleaseStringUTFChars(dest_path, destPathStr);
        return result;
    }
    return env->NewObject(gParcelFileDescriptorOffsets.mClass,
        gParcelFileDescriptorOffsets.mConstructor, fileDescriptor);
#endif
    return NULL;
}
@@ -243,8 +215,8 @@ static JNINativeMethod gMethods[] = {
    {"native_delete_object",   "(IJ)Z", (void *)android_media_MtpClient_delete_object},
    {"native_get_parent",      "(IJ)J", (void *)android_media_MtpClient_get_parent},
    {"native_get_storage_id",  "(IJ)J", (void *)android_media_MtpClient_get_storage_id},
    {"native_open_file",       "(IJ)Landroid/os/ParcelFileDescriptor;",
                                        (void *)android_media_MtpClient_open_file},
    {"native_import_file",     "(IJLjava/lang/String;)Z",
                                        (void *)android_media_MtpClient_import_file},
};

static const char* const kClassPathName = "android/media/MtpClient";
@@ -276,21 +248,6 @@ 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));
}
+17 −13
Original line number Diff line number Diff line
@@ -413,20 +413,32 @@ int MtpDataPacket::read(struct usb_endpoint *ep) {
}

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);
        int ret = transfer(ep, (char *)buffer + read, length - read);
        if (ret < 0) {
printf("MtpDataPacket::readData returning %d\n", ret);
            return ret;
        }
        read += ret;
    }
printf("MtpDataPacket::readData returning %d\n", read);
    return read;
}

// Queue a read request.  Call readDataWait to wait for result
int MtpDataPacket::readDataAsync(struct usb_endpoint *ep, void* buffer, int length) {
    if (usb_endpoint_queue(ep, buffer, length)) {
        LOGE("usb_endpoint_queue failed, errno: %d", errno);
        return -1;
    }
    return 0;
}

// Wait for result of readDataAsync
int MtpDataPacket::readDataWait(struct usb_endpoint *ep) {
    int ep_num;
    return usb_endpoint_wait(usb_endpoint_get_device(ep), &ep_num);
}

int MtpDataPacket::readDataHeader(struct usb_endpoint *ep) {
    int length = transfer(ep, mBuffer, usb_endpoint_max_packet(ep));
    if (length >= 0)
@@ -454,15 +466,7 @@ int MtpDataPacket::write(struct usb_endpoint *ep) {
}

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;
    }
    int ret = transfer(ep, buffer, length);
    return (ret < 0 ? ret : 0);
}

Loading