diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java b/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java index aafe8846b8c38..4649340406e0b 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/Identifier.java @@ -19,14 +19,44 @@ package com.android.mtp; /** * Static utilities for ID. */ -abstract class Identifier { - // TODO: Make the ID persistent. - static String createRootId(long deviceId, long storageId) { - return String.format("%d:%d", deviceId, storageId); +class Identifier { + int mDeviceId; + int mStorageId; + int mObjectHandle; + + static Identifier createFromRootId(String rootId) { + final String[] components = rootId.split(":"); + return new Identifier( + Integer.parseInt(components[0]), + Integer.parseInt(components[1])); + } + + static Identifier createFromDocumentId(String documentId) { + final String[] components = documentId.split(":"); + return new Identifier( + Integer.parseInt(components[0]), + Integer.parseInt(components[1]), + Integer.parseInt(components[2])); + } + + + Identifier(int deviceId, int storageId) { + this(deviceId, storageId, MtpDocument.DUMMY_HANDLE_FOR_ROOT); + } + + Identifier(int deviceId, int storageId, int objectHandle) { + mDeviceId = deviceId; + mStorageId = storageId; + mObjectHandle = objectHandle; } // TODO: Make the ID persistent. - static String createDocumentId(String rootId, long objectHandle) { - return String.format("%s:%d", rootId, objectHandle); + String toRootId() { + return String.format("%d:%d", mDeviceId, mStorageId); + } + + // TODO: Make the ID persistent. + String toDocumentId() { + return String.format("%d:%d:%d", mDeviceId, mStorageId, mObjectHandle); } } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocument.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocument.java index 12eb91e7e8657..5222826bed482 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocument.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocument.java @@ -16,8 +16,99 @@ package com.android.mtp; +import android.mtp.MtpObjectInfo; +import android.provider.DocumentsContract; +import android.provider.DocumentsContract.Document; + +import java.util.Date; + class MtpDocument { static final int DUMMY_HANDLE_FOR_ROOT = 0; - // TODO: Implement model class for MTP document. + private final int mObjectHandle; + private final int mFormat; + private final String mName; + private final Date mDateModified; + private final int mSize; + private final int mThumbSize; + + /** + * Constructor for root document. + */ + MtpDocument(MtpRoot root) { + this(DUMMY_HANDLE_FOR_ROOT, + 0x3001, // Directory. + root.mDescription, + null, // Unknown, + (int) Math.min(root.mMaxCapacity - root.mFreeSpace, Integer.MAX_VALUE), + 0); + } + + MtpDocument(MtpObjectInfo objectInfo) { + this(objectInfo.getObjectHandle(), + objectInfo.getFormat(), + objectInfo.getName(), + objectInfo.getDateModified() != 0 ? new Date(objectInfo.getDateModified()) : null, + objectInfo.getCompressedSize(), + objectInfo.getThumbCompressedSize()); + } + + MtpDocument(int objectHandle, + int format, + String name, + Date dateModified, + int size, + int thumbSize) { + this.mObjectHandle = objectHandle; + this.mFormat = format; + this.mName = name; + this.mDateModified = dateModified; + this.mSize = size; + this.mThumbSize = thumbSize; + } + + String getMimeType() { + // TODO: Add complete list of mime types. + switch (mFormat) { + case 0x3001: + return DocumentsContract.Document.MIME_TYPE_DIR; + case 0x3009: + return "audio/mp3"; + case 0x3801: + return "image/jpeg"; + default: + return ""; + } + } + + Object[] getRow(Identifier rootIdentifier, String[] columnNames) { + final Object[] rows = new Object[columnNames.length]; + for (int i = 0; i < columnNames.length; i++) { + if (Document.COLUMN_DOCUMENT_ID.equals(columnNames[i])) { + final Identifier identifier = new Identifier( + rootIdentifier.mDeviceId, rootIdentifier.mStorageId, mObjectHandle); + rows[i] = identifier.toDocumentId(); + } else if (Document.COLUMN_DISPLAY_NAME.equals(columnNames[i])) { + rows[i] = mName; + } else if (Document.COLUMN_MIME_TYPE.equals(columnNames[i])) { + rows[i] = getMimeType(); + } else if (Document.COLUMN_LAST_MODIFIED.equals(columnNames[i])) { + rows[i] = mDateModified != null ? mDateModified.getTime() : null; + } else if (Document.COLUMN_FLAGS.equals(columnNames[i])) { + int flag = 0; + if (mObjectHandle != DUMMY_HANDLE_FOR_ROOT) { + flag |= DocumentsContract.Document.FLAG_SUPPORTS_DELETE; + if (mThumbSize > 0) { + flag |= DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL; + } + } + rows[i] = flag; + } else if (Document.COLUMN_SIZE.equals(columnNames[i])) { + rows[i] = mSize; + } else { + rows[i] = null; + } + } + return rows; + } } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java index 58203c292196f..fab512563448a 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDocumentsProvider.java @@ -87,14 +87,14 @@ public class MtpDocumentsProvider extends DocumentsProvider { // TODO: Add retry logic here. for (final MtpRoot root : roots) { - final String rootId = Identifier.createRootId(deviceId, root.mStorageId); + final Identifier rootIdentifier = new Identifier(deviceId, root.mStorageId); final MatrixCursor.RowBuilder builder = cursor.newRow(); - builder.add(Root.COLUMN_ROOT_ID, rootId); + builder.add(Root.COLUMN_ROOT_ID, rootIdentifier.toRootId()); builder.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_IS_CHILD); builder.add(Root.COLUMN_TITLE, root.mDescription); builder.add( Root.COLUMN_DOCUMENT_ID, - Identifier.createDocumentId(rootId, MtpDocument.DUMMY_HANDLE_FOR_ROOT)); + rootIdentifier.toDocumentId()); builder.add(Root.COLUMN_AVAILABLE_BYTES , root.mFreeSpace); } } catch (IOException error) { @@ -109,13 +109,49 @@ public class MtpDocumentsProvider extends DocumentsProvider { @Override public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException { - throw new FileNotFoundException(); + if (projection == null) { + projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION; + } + final Identifier identifier = Identifier.createFromDocumentId(documentId); + + MtpDocument document = null; + if (identifier.mObjectHandle != MtpDocument.DUMMY_HANDLE_FOR_ROOT) { + try { + document = mMtpManager.getDocument(identifier.mDeviceId, identifier.mObjectHandle); + } catch (IOException e) { + throw new FileNotFoundException(e.getMessage()); + } + } else { + MtpRoot[] roots; + try { + roots = mMtpManager.getRoots(identifier.mDeviceId); + if (roots != null) { + for (final MtpRoot root : roots) { + if (identifier.mStorageId == root.mStorageId) { + document = new MtpDocument(root); + break; + } + } + } + if (document == null) { + throw new FileNotFoundException(); + } + } catch (IOException e) { + throw new FileNotFoundException(e.getMessage()); + } + } + + final MatrixCursor cursor = new MatrixCursor(projection); + cursor.addRow(document.getRow( + new Identifier(identifier.mDeviceId, identifier.mStorageId), + projection)); + return cursor; } @Override - public Cursor queryChildDocuments(String parentDocumentId, - String[] projection, String sortOrder) - throws FileNotFoundException { + public Cursor queryChildDocuments( + String parentDocumentId, String[] projection, String sortOrder) + throws FileNotFoundException { throw new FileNotFoundException(); } diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java index 05e3a72b3f9af..e34312c6c3d36 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpManager.java @@ -98,6 +98,11 @@ class MtpManager { return results; } + synchronized MtpDocument getDocument(int deviceId, int objectHandle) throws IOException { + final MtpDevice device = getDevice(deviceId); + return new MtpDocument(device.getObjectInfo(objectHandle)); + } + 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/MtpRoot.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpRoot.java index ab1688272957b..fb160aa1e0c9f 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpRoot.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpRoot.java @@ -21,14 +21,14 @@ import android.mtp.MtpStorageInfo; import com.android.internal.annotations.VisibleForTesting; class MtpRoot { - final long mStorageId; + final int mStorageId; final String mDescription; final long mFreeSpace; final long mMaxCapacity; final String mVolumeIdentifier; @VisibleForTesting - MtpRoot(long storageId, + MtpRoot(int storageId, String description, long freeSpace, long maxCapacity, diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java index bcff6c9de3a03..81fff21e2d08f 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDocumentsProviderTest.java @@ -19,12 +19,14 @@ package com.android.mtp; import android.database.ContentObserver; import android.database.Cursor; import android.net.Uri; +import android.provider.DocumentsContract; import android.provider.DocumentsContract.Root; import android.test.AndroidTestCase; import android.test.mock.MockContentResolver; import android.test.suitebuilder.annotation.SmallTest; import java.io.IOException; +import java.util.Date; @SmallTest public class MtpDocumentsProviderTest extends AndroidTestCase { @@ -167,6 +169,50 @@ public class MtpDocumentsProviderTest extends AndroidTestCase { } } + public void testQueryDocument() throws IOException { + mMtpManager.setDocument(0, 2, new MtpDocument( + 2 /* object handle */, + 0x3801 /* JPEG */, + "image.jpg" /* display name */, + new Date(1422716400000L) /* modified date */, + 1024 * 1024 * 5 /* file size */, + 1024 * 50 /* thumbnail size */)); + final Cursor cursor = mProvider.queryDocument("0:1:2", null); + assertEquals(1, cursor.getCount()); + + cursor.moveToNext(); + assertEquals("0:1:2", cursor.getString(0)); + assertEquals("image/jpeg", cursor.getString(1)); + assertEquals("image.jpg", cursor.getString(2)); + assertEquals(1422716400000L, cursor.getLong(3)); + assertEquals( + DocumentsContract.Document.FLAG_SUPPORTS_DELETE | + DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL, + cursor.getInt(4)); + assertEquals(1024 * 1024 * 5, cursor.getInt(5)); + } + + public void testQueryDocument_forRoot() throws IOException { + mMtpManager.setRoots(0, new MtpRoot[] { + new MtpRoot( + 1 /* storageId */, + "Storage A" /* volume description */, + 1024 /* free space */, + 4096 /* total space */, + "" /* no volume identifier */) + }); + final Cursor cursor = mProvider.queryDocument("0:1:0", null); + assertEquals(1, cursor.getCount()); + + cursor.moveToNext(); + assertEquals("0:1:0", cursor.getString(0)); + assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1)); + assertEquals("Storage A", cursor.getString(2)); + assertTrue(cursor.isNull(3)); + assertEquals(0, cursor.getInt(4)); + assertEquals(3072, cursor.getInt(5)); + } + private static class ContentResolver extends MockContentResolver { int changeCount = 0; diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java index ac13741eb714a..08ceb293c3a67 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/TestMtpManager.java @@ -19,6 +19,7 @@ package com.android.mtp; import android.content.Context; import java.io.IOException; +import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Map; @@ -26,9 +27,14 @@ import java.util.Set; import java.util.TreeSet; public class TestMtpManager extends MtpManager { + private static String pack(int... args) { + return Arrays.toString(args); + } + private final Set mValidDevices = new HashSet(); private final Set mOpenedDevices = new TreeSet(); private final Map mRoots = new HashMap(); + private final Map mDocuments = new HashMap(); TestMtpManager(Context context) { super(context); @@ -42,6 +48,10 @@ public class TestMtpManager extends MtpManager { mRoots.put(deviceId, roots); } + void setDocument(int deviceId, int objectHandle, MtpDocument document) { + mDocuments.put(pack(deviceId, objectHandle), document); + } + @Override void openDevice(int deviceId) throws IOException { if (!mValidDevices.contains(deviceId) || mOpenedDevices.contains(deviceId)) { @@ -67,6 +77,11 @@ public class TestMtpManager extends MtpManager { } } + @Override + MtpDocument getDocument(int deviceId, int objectHandle) { + return mDocuments.get(pack(deviceId, objectHandle)); + } + @Override int[] getOpenedDeviceIds() { int i = 0;