Implement MtpDocumentsProvider#queryDocument.
BUG=20274999 Change-Id: Id5c81f744ea1e28d0a0d352b52db1c33fd5edcc2
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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<Integer> mValidDevices = new HashSet<Integer>();
|
||||
private final Set<Integer> mOpenedDevices = new TreeSet<Integer>();
|
||||
private final Map<Integer, MtpRoot[]> mRoots = new HashMap<Integer, MtpRoot[]>();
|
||||
private final Map<String, MtpDocument> mDocuments = new HashMap<String, MtpDocument>();
|
||||
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user