diff options
| author | 2015-08-24 16:10:51 +0900 | |
|---|---|---|
| committer | 2015-08-28 10:50:23 +0900 | |
| commit | b80a3cfd05fc7492dd59b7f8d4337eb5e29088c2 (patch) | |
| tree | a7086b28e34e4abe8ae31e4215ac99c457bf7fd7 | |
| parent | c61b3aabdd9eee52c8847c9c7b09a94bf20fb3f8 (diff) | |
Add support for uploading files via MTP.
Change-Id: Id1811ab70cb28be471e0a99999e9ad5380deac49
12 files changed, 269 insertions, 47 deletions
diff --git a/api/current.txt b/api/current.txt index df74b45b3e03..e9c5727e1027 100644 --- a/api/current.txt +++ b/api/current.txt @@ -18115,7 +18115,7 @@ package android.mtp { method public boolean importFile(int, java.lang.String); method public boolean importFile(int, android.os.ParcelFileDescriptor); method public boolean open(android.hardware.usb.UsbDeviceConnection); - method public boolean sendObject(int, android.os.ParcelFileDescriptor); + method public boolean sendObject(int, int, android.os.ParcelFileDescriptor); method public android.mtp.MtpObjectInfo sendObjectInfo(android.mtp.MtpObjectInfo); } diff --git a/api/system-current.txt b/api/system-current.txt index bcae74b7c45c..e234970b9c80 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -19627,7 +19627,7 @@ package android.mtp { method public boolean importFile(int, java.lang.String); method public boolean importFile(int, android.os.ParcelFileDescriptor); method public boolean open(android.hardware.usb.UsbDeviceConnection); - method public boolean sendObject(int, android.os.ParcelFileDescriptor); + method public boolean sendObject(int, int, android.os.ParcelFileDescriptor); method public android.mtp.MtpObjectInfo sendObjectInfo(android.mtp.MtpObjectInfo); } diff --git a/media/java/android/mtp/MtpDevice.java b/media/java/android/mtp/MtpDevice.java index fbe047dea1d7..3cd157e2b74f 100644 --- a/media/java/android/mtp/MtpDevice.java +++ b/media/java/android/mtp/MtpDevice.java @@ -257,11 +257,12 @@ public final class MtpDevice { * on completion, and must be done by the caller. * * @param objectHandle handle of the target file + * @param size size of the file in bytes * @param descriptor file descriptor to read the data from. * @return true if the file transfer succeeds */ - public boolean sendObject(int objectHandle, ParcelFileDescriptor descriptor) { - return native_send_object(objectHandle, descriptor.getFd()); + public boolean sendObject(int objectHandle, int size, ParcelFileDescriptor descriptor) { + return native_send_object(objectHandle, size, descriptor.getFd()); } /** @@ -294,6 +295,6 @@ public final class MtpDevice { private native long native_get_storage_id(int objectHandle); private native boolean native_import_file(int objectHandle, String destPath); private native boolean native_import_file(int objectHandle, int fd); - private native boolean native_send_object(int objectHandle, int fd); + private native boolean native_send_object(int objectHandle, int size, int fd); private native MtpObjectInfo native_send_object_info(MtpObjectInfo info); } diff --git a/media/java/android/mtp/MtpObjectInfo.java b/media/java/android/mtp/MtpObjectInfo.java index f79d52ee8c56..a080c7355037 100644 --- a/media/java/android/mtp/MtpObjectInfo.java +++ b/media/java/android/mtp/MtpObjectInfo.java @@ -273,25 +273,25 @@ public final class MtpObjectInfo { public Builder(MtpObjectInfo objectInfo) { mObjectInfo = new MtpObjectInfo(); mObjectInfo.mHandle = -1; - mObjectInfo.mAssociationDesc = mObjectInfo.mAssociationDesc; - mObjectInfo.mAssociationType = mObjectInfo.mAssociationType; - mObjectInfo.mCompressedSize = mObjectInfo.mCompressedSize; - mObjectInfo.mDateCreated = mObjectInfo.mDateCreated; - mObjectInfo.mDateModified = mObjectInfo.mDateModified; - mObjectInfo.mFormat = mObjectInfo.mFormat; - mObjectInfo.mImagePixDepth = mObjectInfo.mImagePixDepth; - mObjectInfo.mImagePixHeight = mObjectInfo.mImagePixHeight; - mObjectInfo.mImagePixWidth = mObjectInfo.mImagePixWidth; - mObjectInfo.mKeywords = mObjectInfo.mKeywords; - mObjectInfo.mName = mObjectInfo.mName; - mObjectInfo.mParent = mObjectInfo.mParent; - mObjectInfo.mProtectionStatus = mObjectInfo.mProtectionStatus; - mObjectInfo.mSequenceNumber = mObjectInfo.mSequenceNumber; - mObjectInfo.mStorageId = mObjectInfo.mStorageId; - mObjectInfo.mThumbCompressedSize = mObjectInfo.mThumbCompressedSize; - mObjectInfo.mThumbFormat = mObjectInfo.mThumbFormat; - mObjectInfo.mThumbPixHeight = mObjectInfo.mThumbPixHeight; - mObjectInfo.mThumbPixWidth = mObjectInfo.mThumbPixWidth; + mObjectInfo.mAssociationDesc = objectInfo.mAssociationDesc; + mObjectInfo.mAssociationType = objectInfo.mAssociationType; + mObjectInfo.mCompressedSize = objectInfo.mCompressedSize; + mObjectInfo.mDateCreated = objectInfo.mDateCreated; + mObjectInfo.mDateModified = objectInfo.mDateModified; + mObjectInfo.mFormat = objectInfo.mFormat; + mObjectInfo.mImagePixDepth = objectInfo.mImagePixDepth; + mObjectInfo.mImagePixHeight = objectInfo.mImagePixHeight; + mObjectInfo.mImagePixWidth = objectInfo.mImagePixWidth; + mObjectInfo.mKeywords = objectInfo.mKeywords; + mObjectInfo.mName = objectInfo.mName; + mObjectInfo.mParent = objectInfo.mParent; + mObjectInfo.mProtectionStatus = objectInfo.mProtectionStatus; + mObjectInfo.mSequenceNumber = objectInfo.mSequenceNumber; + mObjectInfo.mStorageId = objectInfo.mStorageId; + mObjectInfo.mThumbCompressedSize = objectInfo.mThumbCompressedSize; + mObjectInfo.mThumbFormat = objectInfo.mThumbFormat; + mObjectInfo.mThumbPixHeight = objectInfo.mThumbPixHeight; + mObjectInfo.mThumbPixWidth = objectInfo.mThumbPixWidth; } public Builder setAssociationDesc(int value) { diff --git a/media/jni/android_mtp_MtpDevice.cpp b/media/jni/android_mtp_MtpDevice.cpp index 9dd3861177e9..2a46ee72e50c 100644 --- a/media/jni/android_mtp_MtpDevice.cpp +++ b/media/jni/android_mtp_MtpDevice.cpp @@ -412,18 +412,13 @@ android_mtp_MtpDevice_import_file_to_fd(JNIEnv *env, jobject thiz, jint object_i } static jboolean -android_mtp_MtpDevice_send_object(JNIEnv *env, jobject thiz, jint object_id, jint fd) +android_mtp_MtpDevice_send_object(JNIEnv *env, jobject thiz, jint object_id, jint size, jint fd) { MtpDevice* device = get_device_from_object(env, thiz); if (!device) return JNI_FALSE; - MtpObjectInfo* object_info = device->getObjectInfo(object_id); - if (!object_info) - return JNI_FALSE; - bool result = device->sendObject(object_info, fd); - delete object_info; - return result; + return device->sendObject(object_id, size, fd); } static jobject @@ -516,7 +511,7 @@ static JNINativeMethod gMethods[] = { {"native_import_file", "(ILjava/lang/String;)Z", (void *)android_mtp_MtpDevice_import_file}, {"native_import_file", "(II)Z",(void *)android_mtp_MtpDevice_import_file_to_fd}, - {"native_send_object", "(II)Z",(void *)android_mtp_MtpDevice_send_object}, + {"native_send_object", "(III)Z",(void *)android_mtp_MtpDevice_send_object}, {"native_send_object_info", "(Landroid/mtp/MtpObjectInfo;)Landroid/mtp/MtpObjectInfo;", (void *)android_mtp_MtpDevice_send_object_info} }; diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocument.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocument.java index 7126694bc4a5..c1d9609eeab1 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocument.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocument.java @@ -76,18 +76,25 @@ class MtpDocument { void addToCursor(Identifier rootIdentifier, MatrixCursor.RowBuilder builder) { final Identifier identifier = new Identifier( rootIdentifier.mDeviceId, rootIdentifier.mStorageId, mObjectHandle); + final String mimeType = formatTypeToMimeType(mFormat); int flag = 0; if (mObjectHandle != DUMMY_HANDLE_FOR_ROOT) { - flag |= DocumentsContract.Document.FLAG_SUPPORTS_DELETE; if (mThumbSize > 0) { flag |= DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL; } + if (!mReadOnly) { + flag |= DocumentsContract.Document.FLAG_SUPPORTS_DELETE | + DocumentsContract.Document.FLAG_SUPPORTS_WRITE; + } + } + if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR && !mReadOnly) { + flag |= DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE; } builder.add(Document.COLUMN_DOCUMENT_ID, identifier.toDocumentId()); builder.add(Document.COLUMN_DISPLAY_NAME, mName); - builder.add(Document.COLUMN_MIME_TYPE, formatTypeToMimeType(mFormat)); + builder.add(Document.COLUMN_MIME_TYPE, mimeType); builder.add( Document.COLUMN_LAST_MODIFIED, mDateModified != null ? mDateModified.getTime() : null); diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java index 031cc074672a..a3cf3e206a96 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java @@ -167,13 +167,18 @@ public class MtpDocumentsProvider extends DocumentsProvider { public ParcelFileDescriptor openDocument( String documentId, String mode, CancellationSignal signal) throws FileNotFoundException { - if (!"r".equals(mode) && !"w".equals(mode)) { - // TODO: Support seekable file. - throw new UnsupportedOperationException("The provider does not support seekable file."); - } final Identifier identifier = Identifier.createFromDocumentId(documentId); try { - return mPipeManager.readDocument(mMtpManager, identifier); + switch (mode) { + case "r": + return mPipeManager.readDocument(mMtpManager, identifier); + case "w": + return mPipeManager.writeDocument(getContext(), mMtpManager, identifier); + default: + // TODO: Add support for seekable files. + throw new UnsupportedOperationException( + "The provider does not support seekable file."); + } } catch (IOException error) { throw new FileNotFoundException(error.getMessage()); } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java index 27ba794d3883..66470094579e 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java @@ -108,6 +108,11 @@ class MtpManager { return results; } + synchronized MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException { + final MtpDevice device = getDevice(deviceId); + return device.getObjectInfo(objectHandle); + } + synchronized MtpDocument getDocument(int deviceId, int objectHandle) throws IOException { final MtpDevice device = getDevice(deviceId); return new MtpDocument(device.getObjectInfo(objectHandle)); @@ -137,15 +142,20 @@ class MtpManager { } } + // TODO: Remove this method. synchronized int createDocument(int deviceId, int storageId, int parentObjectHandle, String mimeType, String name) throws IOException { - final MtpDevice device = getDevice(deviceId); final MtpObjectInfo objectInfo = new MtpObjectInfo.Builder() .setName(name) .setStorageId(storageId) .setParent(parentObjectHandle) .setFormat(MtpDocument.mimeTypeToFormatType(mimeType)) .build(); + return createDocument(deviceId, objectInfo); + } + + synchronized int createDocument(int deviceId, MtpObjectInfo objectInfo) throws IOException { + final MtpDevice device = getDevice(deviceId); final MtpObjectInfo result = device.sendObjectInfo(objectInfo); if (result == null) { throw new IOException("Failed to create a document"); @@ -168,6 +178,13 @@ class MtpManager { device.importFile(objectHandle, target); } + synchronized void sendObject(int deviceId, int objectHandle, int size, + ParcelFileDescriptor source) throws IOException { + final MtpDevice device = getDevice(deviceId); + if (!device.sendObject(objectHandle, size, source)) + throw new IOException("Failed to send a document"); + } + private MtpDevice getDevice(int deviceId) throws IOException { final MtpDevice device = mDevices.get(deviceId); if (device == null) { diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java index ba13b31f7a1f..53f1b65a6f79 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/PipeManager.java @@ -16,10 +16,16 @@ package com.android.mtp; +import android.content.Context; +import android.mtp.MtpObjectInfo; import android.os.ParcelFileDescriptor; import android.util.Log; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; +import java.io.OutputStream; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -40,6 +46,13 @@ class PipeManager { return task.getReadingFileDescriptor(); } + ParcelFileDescriptor writeDocument(Context context, MtpManager model, Identifier identifier) + throws IOException { + final Task task = new WriteDocumentTask(context, model, identifier); + mExecutor.execute(task); + return task.getWritingFileDescriptor(); + } + ParcelFileDescriptor readThumbnail(MtpManager model, Identifier identifier) throws IOException { final Task task = new GetThumbnailTask(model, identifier); mExecutor.execute(task); @@ -60,6 +73,10 @@ class PipeManager { ParcelFileDescriptor getReadingFileDescriptor() { return mDescriptors[0]; } + + ParcelFileDescriptor getWritingFileDescriptor() { + return mDescriptors[1]; + } } private static class ImportFileTask extends Task { @@ -83,6 +100,70 @@ class PipeManager { } } + private static class WriteDocumentTask extends Task { + private final Context mContext; + + WriteDocumentTask(Context context, MtpManager model, Identifier identifier) + throws IOException { + super(model, identifier); + mContext = context; + } + + @Override + public void run() { + File tempFile = null; + try { + // Obtain a temporary file and copy the data to it. + tempFile = mContext.getCacheDir().createTempFile("mtp", "tmp"); + try ( + final FileOutputStream tempOutputStream = + new ParcelFileDescriptor.AutoCloseOutputStream( + ParcelFileDescriptor.open( + tempFile, ParcelFileDescriptor.MODE_WRITE_ONLY)); + final ParcelFileDescriptor.AutoCloseInputStream inputStream = + new ParcelFileDescriptor.AutoCloseInputStream(mDescriptors[0]) + ) { + final byte[] buffer = new byte[32 * 1024]; + int bytes; + while ((bytes = inputStream.read(buffer)) != -1) { + mDescriptors[0].checkError(); + tempOutputStream.write(buffer, 0, bytes); + } + tempOutputStream.flush(); + } + + // Get the placeholder object info. + final MtpObjectInfo placeholderObjectInfo = + mManager.getObjectInfo(mIdentifier.mDeviceId, mIdentifier.mObjectHandle); + + // Delete the target object info if it already exists (as a placeholder). + mManager.deleteDocument(mIdentifier.mDeviceId, mIdentifier.mObjectHandle); + + // Create the target object info with a correct file size. + final int targetObjectHandle = + mManager.createDocument( + mIdentifier.mDeviceId, + new MtpObjectInfo.Builder(placeholderObjectInfo) + .setCompressedSize((int) tempFile.length()) + .build()); + + // Upload the object. + final ParcelFileDescriptor tempInputDescriptor = ParcelFileDescriptor.open( + tempFile, ParcelFileDescriptor.MODE_READ_ONLY); + mManager.sendObject(mIdentifier.mDeviceId, + targetObjectHandle, (int) tempFile.length(), tempInputDescriptor); + + } catch (IOException error) { + Log.w(MtpDocumentsProvider.TAG, + "Failed to send a file because of: " + error.getMessage()); + } finally { + if (tempFile != null) { + tempFile.delete(); + } + } + } + } + private static class GetThumbnailTask extends Task { GetThumbnailTask(MtpManager model, Identifier identifier) throws IOException { super(model, identifier); diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java index 16be669bd70e..1826bd077e22 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java @@ -211,7 +211,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { new Date(1422716400000L) /* modified date */, 1024 * 1024 * 5 /* file size */, 1024 * 50 /* thumbnail size */, - true /* read only */)); + false /* read only */)); final Cursor cursor = mProvider.queryDocument("0_1_2", null); assertEquals(1, cursor.getCount()); @@ -222,11 +222,37 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { assertEquals(1422716400000L, cursor.getLong(3)); assertEquals( DocumentsContract.Document.FLAG_SUPPORTS_DELETE | + DocumentsContract.Document.FLAG_SUPPORTS_WRITE | DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL, cursor.getInt(4)); assertEquals(1024 * 1024 * 5, cursor.getInt(5)); } + public void testQueryDocument_directory() throws IOException { + mMtpManager.setDocument(0, 2, new MtpDocument( + 2 /* object handle */, + 0x3001 /* directory */, + "directory" /* display name */, + new Date(1422716400000L) /* modified date */, + 0 /* file size */, + 0 /* thumbnail size */, + false /* read only */)); + final Cursor cursor = mProvider.queryDocument("0_1_2", null); + assertEquals(1, cursor.getCount()); + + cursor.moveToNext(); + assertEquals("0_1_2", cursor.getString(0)); + assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1)); + assertEquals("directory", cursor.getString(2)); + assertEquals(1422716400000L, cursor.getLong(3)); + assertEquals( + DocumentsContract.Document.FLAG_SUPPORTS_DELETE | + DocumentsContract.Document.FLAG_SUPPORTS_WRITE | + DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE, + cursor.getInt(4)); + assertEquals(0, cursor.getInt(5)); + } + public void testQueryDocument_forRoot() throws IOException { mMtpManager.setRoots(0, new MtpRoot[] { new MtpRoot( @@ -269,10 +295,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { assertEquals("image/jpeg", cursor.getString(1)); assertEquals("image.jpg", cursor.getString(2)); assertEquals(0, cursor.getLong(3)); - assertEquals( - DocumentsContract.Document.FLAG_SUPPORTS_DELETE | - DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL, - cursor.getInt(4)); + assertEquals(DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL, cursor.getInt(4)); assertEquals(1024 * 1024 * 5, cursor.getInt(5)); assertFalse(cursor.moveToNext()); diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java index 35918e1e8bd1..e2cc3ed20302 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/PipeManagerTest.java @@ -21,6 +21,7 @@ import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import java.io.IOException; +import java.util.Date; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; @@ -53,6 +54,41 @@ public class PipeManagerTest extends AndroidTestCase { assertDescriptorError(descriptor); } + public void testWriteDocument_basic() throws Exception { + // Create a placeholder file which should be replaced by a real file later. + mtpManager.setDocument(0, 1, new MtpDocument(1, 0, "", new Date(), 0, 0, false)); + + // Upload testing bytes. + final ParcelFileDescriptor descriptor = pipeManager.writeDocument( + getContext(), mtpManager, new Identifier(0, 0, 1)); + final ParcelFileDescriptor.AutoCloseOutputStream outputStream = + new ParcelFileDescriptor.AutoCloseOutputStream(descriptor); + outputStream.write(HELLO_BYTES, 0, HELLO_BYTES.length); + outputStream.close(); + executor.awaitTermination(1000, TimeUnit.MILLISECONDS); + + // Check if the placeholder file is removed. + try { + final MtpDocument placeholderDocument = mtpManager.getDocument(0, 1); + fail(); // The placeholder file has not been deleted. + } catch (IOException e) { + // Expected error, as the file is gone. + } + + // Confirm that the target file is created. + final MtpDocument targetDocument = mtpManager.getDocument( + 0, TestMtpManager.CREATED_DOCUMENT_HANDLE); + assertTrue(targetDocument != null); + + // Verify uploaded bytes. + final byte[] uploadedBytes = mtpManager.getImportFileBytes( + 0, TestMtpManager.CREATED_DOCUMENT_HANDLE); + assertEquals(HELLO_BYTES.length, uploadedBytes.length); + for (int i = 0; i < HELLO_BYTES.length; i++) { + assertEquals(HELLO_BYTES[i], uploadedBytes[i]); + } + } + public void testReadThumbnail_basic() throws Exception { mtpManager.setThumbnail(0, 1, HELLO_BYTES); final ParcelFileDescriptor descriptor = pipeManager.readThumbnail( diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java index 40de7b40923e..94b5ba08b036 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java @@ -17,10 +17,13 @@ package com.android.mtp; import android.content.Context; +import android.mtp.MtpObjectInfo; +import android.mtp.MtpObjectInfo.Builder; import android.os.ParcelFileDescriptor; import java.io.IOException; import java.util.Arrays; +import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -28,6 +31,8 @@ import java.util.Set; import java.util.TreeSet; public class TestMtpManager extends MtpManager { + public static final int CREATED_DOCUMENT_HANDLE = 1000; + protected static String pack(int... args) { return Arrays.toString(args); } @@ -65,6 +70,10 @@ public class TestMtpManager extends MtpManager { mImportFileBytes.put(pack(deviceId, objectHandle), bytes); } + byte[] getImportFileBytes(int deviceId, int objectHandle) { + return mImportFileBytes.get(pack(deviceId, objectHandle)); + } + void setThumbnail(int deviceId, int objectHandle, byte[] bytes) { mThumbnailBytes.put(pack(deviceId, objectHandle), bytes); } @@ -109,6 +118,15 @@ public class TestMtpManager extends MtpManager { } @Override + MtpObjectInfo getObjectInfo(int deviceId, int objectHandle) throws IOException { + final MtpDocument document = getDocument(deviceId, objectHandle); + // It's impossible to set an object id of MtpObjectInfo at this stage. Also, + // it's hard to get any information from MtpDocument, as it's designed to return them + // only via cursors. Rework these. + return new MtpObjectInfo.Builder().build(); + } + + @Override int[] getObjectHandles(int deviceId, int storageId, int parentObjectHandle) throws IOException { final String key = pack(deviceId, storageId, parentObjectHandle); if (mObjectHandles.containsKey(key)) { @@ -119,8 +137,9 @@ public class TestMtpManager extends MtpManager { } @Override - void importFile(int deviceId, int storageId, ParcelFileDescriptor target) throws IOException { - final String key = pack(deviceId, storageId); + void importFile(int deviceId, int objectHandle, ParcelFileDescriptor target) + throws IOException { + final String key = pack(deviceId, objectHandle); if (mImportFileBytes.containsKey(key)) { try (final ParcelFileDescriptor.AutoCloseOutputStream outputStream = new ParcelFileDescriptor.AutoCloseOutputStream(target)) { @@ -132,6 +151,44 @@ public class TestMtpManager extends MtpManager { } @Override + int createDocument(int deviceId, MtpObjectInfo objectInfo) throws IOException { + // For simplicity, it allows to create only one document, and it always has the hardcoded + // CREATED_DOCUMENT_HANDLE document handle. + final String key = pack(deviceId, CREATED_DOCUMENT_HANDLE); + if (!mDocuments.containsKey(key)) { + mDocuments.put(key, new MtpDocument( + CREATED_DOCUMENT_HANDLE, + objectInfo.getFormat(), + objectInfo.getName(), + new Date(objectInfo.getDateModified()), + objectInfo.getCompressedSize(), + objectInfo.getThumbCompressedSize(), + false /* Always writable for testing. */)); + } else { + throw new IOException(); + } + return CREATED_DOCUMENT_HANDLE; + } + + @Override + void sendObject(int deviceId, int objectHandle, int size, ParcelFileDescriptor source) + throws IOException { + final String key = pack(deviceId, objectHandle); + if (!mDocuments.containsKey(key)) { + throw new IOException(); + } + + ParcelFileDescriptor.AutoCloseInputStream inputStream = + new ParcelFileDescriptor.AutoCloseInputStream(source); + byte[] buffer = new byte[size]; + if (inputStream.read(buffer, 0, size) != size) { + throw new IOException(); + } + + mImportFileBytes.put(pack(deviceId, objectHandle), buffer); + } + + @Override byte[] getThumbnail(int deviceId, int objectHandle) throws IOException { final String key = pack(deviceId, objectHandle); if (mThumbnailBytes.containsKey(key)) { |