Add support for uploading files via MTP.
Change-Id: Id1811ab70cb28be471e0a99999e9ad5380deac49
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -108,6 +117,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);
|
||||
@@ -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)) {
|
||||
@@ -131,6 +150,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);
|
||||
|
||||
Reference in New Issue
Block a user