Change ID format of MtpDocumentsProvider.

BUG=25704562

Change-Id: I5d9fc167512eee06964650e07206e226173611b2
This commit is contained in:
Daichi Hirono
2015-11-19 16:13:38 +09:00
parent 74bfa27199
commit 9e8a4fa78f
11 changed files with 208 additions and 322 deletions

View File

@@ -1,91 +0,0 @@
/*
* Copyright (C) 2015 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.mtp;
import android.content.res.Resources;
import android.database.MatrixCursor;
import android.media.MediaFile;
import android.mtp.MtpConstants;
import android.mtp.MtpObjectInfo;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
/**
* TODO Remove this class after we switch to use MtpDatabase.
*/
final class CursorHelper {
static final int DUMMY_HANDLE_FOR_ROOT = 0;
private CursorHelper() {
}
static void addToCursor(Resources resources, MtpRoot root, MatrixCursor.RowBuilder builder) {
final Identifier identifier = new Identifier(
root.mDeviceId, root.mStorageId, DUMMY_HANDLE_FOR_ROOT);
builder.add(Document.COLUMN_DOCUMENT_ID, identifier.toDocumentId());
builder.add(Document.COLUMN_DISPLAY_NAME, root.getRootName(resources));
builder.add(Document.COLUMN_MIME_TYPE, DocumentsContract.Document.MIME_TYPE_DIR);
builder.add(Document.COLUMN_LAST_MODIFIED, null);
builder.add(Document.COLUMN_FLAGS, 0);
builder.add(Document.COLUMN_SIZE,
(int) Math.min(root.mMaxCapacity - root.mFreeSpace, Integer.MAX_VALUE));
}
static void addToCursor(MtpObjectInfo objectInfo, Identifier rootIdentifier,
MatrixCursor.RowBuilder builder) {
final Identifier identifier = new Identifier(
rootIdentifier.mDeviceId, rootIdentifier.mStorageId, objectInfo.getObjectHandle());
final String mimeType = formatTypeToMimeType(objectInfo.getFormat());
int flag = 0;
if (objectInfo.getProtectionStatus() == 0) {
flag |= DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
DocumentsContract.Document.FLAG_SUPPORTS_WRITE;
if (mimeType == DocumentsContract.Document.MIME_TYPE_DIR) {
flag |= DocumentsContract.Document.FLAG_DIR_SUPPORTS_CREATE;
}
}
if (objectInfo.getThumbCompressedSize() > 0) {
flag |= DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL;
}
builder.add(Document.COLUMN_DOCUMENT_ID, identifier.toDocumentId());
builder.add(Document.COLUMN_DISPLAY_NAME, objectInfo.getName());
builder.add(Document.COLUMN_MIME_TYPE, mimeType);
builder.add(
Document.COLUMN_LAST_MODIFIED,
objectInfo.getDateModified() != 0 ? objectInfo.getDateModified() : null);
builder.add(Document.COLUMN_FLAGS, flag);
builder.add(Document.COLUMN_SIZE, objectInfo.getCompressedSize());
}
static String formatTypeToMimeType(int format) {
if (format == MtpConstants.FORMAT_ASSOCIATION) {
return DocumentsContract.Document.MIME_TYPE_DIR;
} else {
return MediaFile.getMimeTypeForFormatCode(format);
}
}
static int mimeTypeToFormatType(String fileName, String mimeType) {
if (Document.MIME_TYPE_DIR.equals(mimeType)) {
return MtpConstants.FORMAT_ASSOCIATION;
} else {
return MediaFile.getFormatCode(fileName, mimeType);
}
}
}

View File

@@ -24,7 +24,6 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Process;
import android.provider.DocumentsContract;
import android.util.Log;
import java.io.FileNotFoundException;
import java.io.IOException;
@@ -75,7 +74,7 @@ class DocumentLoader {
int parentHandle = parent.mObjectHandle;
// Need to pass the special value MtpManager.OBJECT_HANDLE_ROOT_CHILDREN to
// getObjectHandles if we would like to obtain children under the root.
if (parentHandle == CursorHelper.DUMMY_HANDLE_FOR_ROOT) {
if (parentHandle == Identifier.DUMMY_HANDLE_FOR_ROOT) {
parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN;
}
// TODO: Handle nit race around here.
@@ -221,8 +220,8 @@ class DocumentLoader {
throw new IOException(mError);
}
final Cursor cursor = mDatabase.queryChildDocuments(
columnNames, mIdentifier.mDocumentId, /* use old ID format */ true);
final Cursor cursor =
mDatabase.queryChildDocuments(columnNames, mIdentifier.mDocumentId);
cursor.setNotificationUri(resolver, createUri());
cursor.respond(extras);
@@ -282,7 +281,7 @@ class DocumentLoader {
private Uri createUri() {
return DocumentsContract.buildChildDocumentsUri(
MtpDocumentsProvider.AUTHORITY, mIdentifier.toDocumentId());
MtpDocumentsProvider.AUTHORITY, mIdentifier.mDocumentId);
}
}
}

View File

@@ -16,39 +16,19 @@
package com.android.mtp;
import java.util.Objects;
/**
* Static utilities for ID.
*/
class Identifier {
final static int DUMMY_HANDLE_FOR_ROOT = 0;
final int mDeviceId;
final int mStorageId;
final int mObjectHandle;
final String mDocumentId;
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, CursorHelper.DUMMY_HANDLE_FOR_ROOT);
}
Identifier(int deviceId, int storageId, int objectHandle) {
this(deviceId, storageId, objectHandle, null);
}
Identifier(int deviceId, int storageId, int objectHandle, String documentId) {
mDeviceId = deviceId;
mStorageId = storageId;
@@ -56,16 +36,6 @@ class Identifier {
mDocumentId = documentId;
}
// TODO: Make the ID persistent.
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);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Identifier))
@@ -77,6 +47,6 @@ class Identifier {
@Override
public int hashCode() {
return (mDeviceId << 16) ^ (mStorageId << 8) ^ mObjectHandle;
return Objects.hash(mDeviceId, mStorageId, mObjectHandle, mDocumentId);
}
}

View File

@@ -22,6 +22,8 @@ import android.content.ContentValues;
import android.content.Context;
import android.content.res.Resources;
import android.database.Cursor;
import android.media.MediaFile;
import android.mtp.MtpConstants;
import android.mtp.MtpObjectInfo;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
@@ -67,10 +69,8 @@ import java.util.Map;
* the database tries to find corresponding rows by using document's name instead of MTP identifier
* at the next update cycle.
*
* TODO: Remove @VisibleForTesting annotation when we start to use this class.
* TODO: Improve performance by SQL optimization.
*/
@VisibleForTesting
class MtpDatabase {
private final MtpDatabaseInternal mDatabase;
@@ -111,38 +111,21 @@ class MtpDatabase {
/**
* {@link MtpDatabaseInternal#queryChildDocuments}
*/
@VisibleForTesting
Cursor queryChildDocuments(String[] columnNames, String parentDocumentId) {
return queryChildDocuments(columnNames, parentDocumentId, false);
}
@VisibleForTesting
Cursor queryChildDocuments(String[] columnNames, String parentDocumentId, boolean useOldId) {
final String[] newColumnNames = new String[columnNames.length];
// TODO: Temporary replace document ID with old format.
for (int i = 0; i < columnNames.length; i++) {
if (useOldId && DocumentsContract.Document.COLUMN_DOCUMENT_ID.equals(columnNames[i])) {
newColumnNames[i] = COLUMN_DEVICE_ID + " || '_' || " + COLUMN_STORAGE_ID +
" || '_' || IFNULL(" + COLUMN_OBJECT_HANDLE + ",0) AS " +
DocumentsContract.Document.COLUMN_DOCUMENT_ID;
} else {
newColumnNames[i] = columnNames[i];
}
}
return mDatabase.queryChildDocuments(newColumnNames, parentDocumentId);
return mDatabase.queryChildDocuments(columnNames, parentDocumentId);
}
/**
* {@link MtpDatabaseInternal#queryDocument}
*/
@VisibleForTesting
Cursor queryDocument(String documentId, String[] projection) {
return mDatabase.queryDocument(documentId, projection);
}
Identifier createIdentifier(String parentDocumentId) {
/**
* {@link MtpDatabaseInternal#createIdentifier}
*/
Identifier createIdentifier(String parentDocumentId) throws FileNotFoundException {
return mDatabase.createIdentifier(parentDocumentId);
}
@@ -157,7 +140,6 @@ class MtpDatabase {
* {@link MtpDatabaseInternal#getParentId}
* @throws FileNotFoundException
*/
@VisibleForTesting
String getParentId(String documentId) throws FileNotFoundException {
return mDatabase.getParentId(documentId);
}
@@ -165,7 +147,6 @@ class MtpDatabase {
/**
* {@link MtpDatabaseInternal#deleteDocument}
*/
@VisibleForTesting
void deleteDocument(String documentId) {
mDatabase.deleteDocument(documentId);
}
@@ -174,7 +155,6 @@ class MtpDatabase {
* {@link MtpDatabaseInternal#putNewDocument}
* @throws FileNotFoundException
*/
@VisibleForTesting
String putNewDocument(int deviceId, String parentDocumentId, MtpObjectInfo info)
throws FileNotFoundException {
final ContentValues values = new ContentValues();
@@ -405,7 +385,9 @@ class MtpDatabase {
private void getChildDocumentValues(
ContentValues values, int deviceId, String parentId, MtpObjectInfo info) {
values.clear();
final String mimeType = CursorHelper.formatTypeToMimeType(info.getFormat());
final String mimeType = info.getFormat() == MtpConstants.FORMAT_ASSOCIATION ?
DocumentsContract.Document.MIME_TYPE_DIR :
MediaFile.getMimeTypeForFormatCode(info.getFormat());
int flag = 0;
if (info.getProtectionStatus() == 0) {
flag |= Document.FLAG_SUPPORTS_DELETE |

View File

@@ -129,11 +129,7 @@ class MtpDatabaseConstants {
Root.COLUMN_TITLE + "," +
TABLE_DOCUMENTS + "." + Document.COLUMN_SUMMARY + " AS " +
Root.COLUMN_SUMMARY + "," +
// Temporary replace COLUMN_DOCUMENT_ID with old format.
TABLE_DOCUMENTS + "." + Document.COLUMN_DOCUMENT_ID + " AS " +
Root.COLUMN_DOCUMENT_ID + "_," +
TABLE_DOCUMENTS + "." + COLUMN_DEVICE_ID + "|| '_' ||" +
TABLE_DOCUMENTS + "." + COLUMN_STORAGE_ID + "||'_0' AS " +
Root.COLUMN_DOCUMENT_ID + "," +
TABLE_ROOT_EXTRA + "." + Root.COLUMN_AVAILABLE_BYTES + "," +
TABLE_ROOT_EXTRA + "." + Root.COLUMN_CAPACITY_BYTES + "," +

View File

@@ -191,6 +191,7 @@ class MtpDatabaseInternal {
String putNewDocument(String parentDocumentId, ContentValues values) {
mDatabase.beginTransaction();
try {
final long id = mDatabase.insert(TABLE_DOCUMENTS, null, values);
mDatabase.setTransactionSuccessful();
return Long.toString(id);
@@ -203,54 +204,28 @@ class MtpDatabaseInternal {
* Gets identifier from document ID.
* @param documentId Document ID.
* @return Identifier.
* @throws FileNotFoundException
*/
Identifier createIdentifier(String documentId) {
// Currently documentId is old format.
final Identifier oldIdentifier = Identifier.createFromDocumentId(documentId);
final String selection;
final String[] args;
if (oldIdentifier.mObjectHandle == CursorHelper.DUMMY_HANDLE_FOR_ROOT) {
selection = COLUMN_DEVICE_ID + "= ? AND " +
COLUMN_ROW_STATE + " IN (?, ?) AND " +
COLUMN_STORAGE_ID + "= ? AND " +
COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
args = strings(
oldIdentifier.mDeviceId,
ROW_STATE_VALID,
ROW_STATE_INVALIDATED,
oldIdentifier.mStorageId);
} else {
selection = COLUMN_DEVICE_ID + "= ? AND " +
COLUMN_ROW_STATE + " IN (?, ?) AND " +
COLUMN_STORAGE_ID + "= ? AND " +
COLUMN_OBJECT_HANDLE + " = ?";
args = strings(
oldIdentifier.mDeviceId,
ROW_STATE_VALID,
ROW_STATE_INVALIDATED,
oldIdentifier.mStorageId,
oldIdentifier.mObjectHandle);
}
Identifier createIdentifier(String documentId) throws FileNotFoundException {
final Cursor cursor = mDatabase.query(
TABLE_DOCUMENTS,
strings(Document.COLUMN_DOCUMENT_ID),
selection,
args,
strings(COLUMN_DEVICE_ID, COLUMN_STORAGE_ID, COLUMN_OBJECT_HANDLE),
SELECTION_DOCUMENT_ID,
strings(documentId),
null,
null,
null,
"1");
try {
if (cursor.getCount() == 0) {
return oldIdentifier;
throw new FileNotFoundException("ID is not found.");
} else {
cursor.moveToNext();
return new Identifier(
oldIdentifier.mDeviceId,
oldIdentifier.mStorageId,
oldIdentifier.mObjectHandle,
cursor.getString(0));
cursor.getInt(0),
cursor.getInt(1),
cursor.isNull(2) ? Identifier.DUMMY_HANDLE_FOR_ROOT : cursor.getInt(2),
documentId);
}
} finally {
cursor.close();
@@ -481,7 +456,8 @@ class MtpDatabaseInternal {
void clearMapping() {
mDatabase.beginTransaction();
try {
deleteDocumentsAndRootsRecursively(COLUMN_ROW_STATE + " = ?", strings(ROW_STATE_PENDING));
deleteDocumentsAndRootsRecursively(
COLUMN_ROW_STATE + " = ?", strings(ROW_STATE_PENDING));
final ContentValues values = new ContentValues();
values.putNull(COLUMN_OBJECT_HANDLE);
values.putNull(COLUMN_STORAGE_ID);
@@ -571,7 +547,6 @@ class MtpDatabaseInternal {
args);
deleted += mDatabase.delete(TABLE_DOCUMENTS, selection, args);
mDatabase.setTransactionSuccessful();
// TODO Remove child.
// TODO Remove mappingState.
return deleted != 0;
} finally {

View File

@@ -22,6 +22,8 @@ import android.content.res.Resources;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.graphics.Point;
import android.media.MediaFile;
import android.mtp.MtpConstants;
import android.mtp.MtpObjectInfo;
import android.os.CancellationSignal;
import android.os.ParcelFileDescriptor;
@@ -116,39 +118,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
if (projection == null) {
projection = MtpDocumentsProvider.DEFAULT_DOCUMENT_PROJECTION;
}
final Identifier identifier = Identifier.createFromDocumentId(documentId);
if (identifier.mObjectHandle != CursorHelper.DUMMY_HANDLE_FOR_ROOT) {
MtpObjectInfo objectInfo;
try {
objectInfo = mMtpManager.getObjectInfo(
identifier.mDeviceId, identifier.mObjectHandle);
} catch (IOException e) {
throw new FileNotFoundException(e.getMessage());
}
final MatrixCursor cursor = new MatrixCursor(projection);
CursorHelper.addToCursor(
objectInfo,
new Identifier(identifier.mDeviceId, identifier.mStorageId),
cursor.newRow());
return cursor;
} else {
MtpRoot[] roots;
try {
roots = mMtpManager.getRoots(identifier.mDeviceId);
} catch (IOException e) {
throw new FileNotFoundException(e.getMessage());
}
for (final MtpRoot root : roots) {
if (identifier.mStorageId != root.mStorageId)
continue;
final MatrixCursor cursor = new MatrixCursor(projection);
CursorHelper.addToCursor(mResources, root, cursor.newRow());
return cursor;
}
}
throw new FileNotFoundException();
return mDatabase.queryDocument(documentId, projection);
}
@Override
@@ -170,7 +140,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
public ParcelFileDescriptor openDocument(
String documentId, String mode, CancellationSignal signal)
throws FileNotFoundException {
final Identifier identifier = Identifier.createFromDocumentId(documentId);
final Identifier identifier = mDatabase.createIdentifier(documentId);
try {
switch (mode) {
case "r":
@@ -195,7 +165,7 @@ public class MtpDocumentsProvider extends DocumentsProvider {
String documentId,
Point sizeHint,
CancellationSignal signal) throws FileNotFoundException {
final Identifier identifier = Identifier.createFromDocumentId(documentId);
final Identifier identifier = mDatabase.createIdentifier(documentId);
try {
return new AssetFileDescriptor(
getPipeManager(identifier).readThumbnail(mMtpManager, identifier),
@@ -209,15 +179,17 @@ public class MtpDocumentsProvider extends DocumentsProvider {
@Override
public void deleteDocument(String documentId) throws FileNotFoundException {
try {
final Identifier identifier = Identifier.createFromDocumentId(documentId);
final int parentHandle =
mMtpManager.getParent(identifier.mDeviceId, identifier.mObjectHandle);
final Identifier identifier = mDatabase.createIdentifier(documentId);
final Identifier parentIdentifier =
mDatabase.createIdentifier(mDatabase.getParentId(documentId));
mMtpManager.deleteDocument(identifier.mDeviceId, identifier.mObjectHandle);
final Identifier parentIdentifier = new Identifier(
identifier.mDeviceId, identifier.mStorageId, parentHandle);
mDatabase.deleteDocument(documentId);
getDocumentLoader(parentIdentifier).clearTask(parentIdentifier);
notifyChildDocumentsChange(parentIdentifier.toDocumentId());
notifyChildDocumentsChange(parentIdentifier.mDocumentId);
} catch (IOException error) {
for (final StackTraceElement element : error.getStackTrace()) {
Log.e("hirono", element.toString());
}
throw new FileNotFoundException(error.getMessage());
}
}
@@ -235,19 +207,23 @@ public class MtpDocumentsProvider extends DocumentsProvider {
public String createDocument(String parentDocumentId, String mimeType, String displayName)
throws FileNotFoundException {
try {
final Identifier parentId = Identifier.createFromDocumentId(parentDocumentId);
final Identifier parentId = mDatabase.createIdentifier(parentDocumentId);
final ParcelFileDescriptor pipe[] = ParcelFileDescriptor.createReliablePipe();
pipe[0].close(); // 0 bytes for a new document.
final int objectHandle = mMtpManager.createDocument(
parentId.mDeviceId,
new MtpObjectInfo.Builder()
.setStorageId(parentId.mStorageId)
.setParent(parentId.mObjectHandle)
.setFormat(CursorHelper.mimeTypeToFormatType(displayName, mimeType))
.setName(displayName)
.build(), pipe[1]);
final String documentId = new Identifier(parentId.mDeviceId, parentId.mStorageId,
objectHandle).toDocumentId();
final int formatCode = Document.MIME_TYPE_DIR.equals(mimeType) ?
MtpConstants.FORMAT_ASSOCIATION :
MediaFile.getFormatCode(displayName, mimeType);
final MtpObjectInfo info = new MtpObjectInfo.Builder()
.setStorageId(parentId.mStorageId)
.setParent(parentId.mObjectHandle)
.setFormat(formatCode)
.setName(displayName)
.build();
final int objectHandle = mMtpManager.createDocument(parentId.mDeviceId, info, pipe[1]);
final MtpObjectInfo infoWithHandle =
new MtpObjectInfo.Builder(info).setObjectHandle(objectHandle).build();
final String documentId = mDatabase.putNewDocument(
parentId.mDeviceId, parentDocumentId, infoWithHandle);
getDocumentLoader(parentId).clearTask(parentId);
notifyChildDocumentsChange(parentDocumentId);
return documentId;

View File

@@ -57,7 +57,7 @@ public class DocumentLoaderTest extends AndroidTestCase {
public void testBasic() throws Exception {
final Uri uri = DocumentsContract.buildChildDocumentsUri(
MtpDocumentsProvider.AUTHORITY, mParentIdentifier.toDocumentId());
MtpDocumentsProvider.AUTHORITY, mParentIdentifier.mDocumentId);
setUpDocument(mManager, 40);
mManager.blockDocument(0, 15);
mManager.blockDocument(0, 35);

View File

@@ -104,7 +104,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
Root.COLUMN_ICON,
Root.COLUMN_TITLE,
Root.COLUMN_SUMMARY,
Root.COLUMN_DOCUMENT_ID + "_",
Root.COLUMN_DOCUMENT_ID,
Root.COLUMN_AVAILABLE_BYTES,
Root.COLUMN_CAPACITY_BYTES
});

View File

@@ -27,6 +27,9 @@ import android.test.suitebuilder.annotation.SmallTest;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.concurrent.TimeoutException;
import static com.android.mtp.MtpDatabaseInternal.strings;
@SmallTest
public class MtpDocumentsProviderTest extends AndroidTestCase {
@@ -138,7 +141,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
// TODO: Add storage icon for MTP devices.
assertTrue(cursor.isNull(2) /* icon */);
assertEquals("Device A Storage A", cursor.getString(3));
assertEquals("0_1_0", cursor.getString(4));
assertEquals("1", cursor.getString(4));
assertEquals(1024, cursor.getInt(5));
}
@@ -154,7 +157,7 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
// TODO: Add storage icon for MTP devices.
assertTrue(cursor.isNull(2) /* icon */);
assertEquals("Device B Storage B", cursor.getString(3));
assertEquals("1_1_0", cursor.getString(4));
assertEquals("2", cursor.getString(4));
assertEquals(2048, cursor.getInt(5));
}
}
@@ -186,29 +189,38 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
// TODO: Add storage icon for MTP devices.
assertTrue(cursor.isNull(2) /* icon */);
assertEquals("Device B Storage B", cursor.getString(3));
assertEquals("1_1_0", cursor.getString(4));
assertEquals("1", cursor.getString(4));
assertEquals(2048, cursor.getInt(5));
}
}
public void testQueryDocument() throws IOException {
public void testQueryDocument() throws IOException, InterruptedException, TimeoutException {
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
.setObjectHandle(2)
.setStorageId(1)
.setFormat(MtpConstants.FORMAT_EXIF_JPEG)
.setName("image.jpg")
.setDateModified(1422716400000L)
.setCompressedSize(1024 * 1024 * 5)
.setThumbCompressedSize(1024 * 50)
.build());
final Cursor cursor = mProvider.queryDocument("0_1_2", null);
setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
setupDocuments(
0,
0,
MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
"1",
new MtpObjectInfo[] {
new MtpObjectInfo.Builder()
.setObjectHandle(100)
.setFormat(MtpConstants.FORMAT_EXIF_JPEG)
.setName("image.jpg")
.setDateModified(1422716400000L)
.setCompressedSize(1024 * 1024 * 5)
.setThumbCompressedSize(50 * 1024)
.build()
});
final Cursor cursor = mProvider.queryDocument("2", null);
assertEquals(1, cursor.getCount());
cursor.moveToNext();
assertEquals("0_1_2", cursor.getString(0));
assertEquals("2", cursor.getString(0));
assertEquals("image/jpeg", cursor.getString(1));
assertEquals("image.jpg", cursor.getString(2));
assertEquals(1422716400000L, cursor.getLong(3));
@@ -220,21 +232,32 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
assertEquals(1024 * 1024 * 5, cursor.getInt(5));
}
public void testQueryDocument_directory() throws IOException {
public void testQueryDocument_directory()
throws IOException, InterruptedException, TimeoutException {
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
.setObjectHandle(2)
.setStorageId(1)
.setFormat(MtpConstants.FORMAT_ASSOCIATION)
.setName("directory")
.setDateModified(1422716400000L)
.build());
final Cursor cursor = mProvider.queryDocument("0_1_2", null);
setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
setupDocuments(
0,
0,
MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
"1",
new MtpObjectInfo[] {
new MtpObjectInfo.Builder()
.setObjectHandle(2)
.setStorageId(1)
.setFormat(MtpConstants.FORMAT_ASSOCIATION)
.setName("directory")
.setDateModified(1422716400000L)
.build()
});
final Cursor cursor = mProvider.queryDocument("2", null);
assertEquals(1, cursor.getCount());
cursor.moveToNext();
assertEquals("0_1_2", cursor.getString(0));
assertEquals("2", cursor.getString(0));
assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
assertEquals("directory", cursor.getString(2));
assertEquals(1422716400000L, cursor.getLong(3));
@@ -246,10 +269,12 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
assertEquals(0, cursor.getInt(5));
}
public void testQueryDocument_forRoot() throws IOException {
public void testQueryDocument_forRoot()
throws IOException, InterruptedException, TimeoutException {
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
mMtpManager.setRoots(0, new MtpRoot[] {
setupRoots(0, new MtpRoot[] {
new MtpRoot(
0 /* deviceId */,
1 /* storageId */,
@@ -259,11 +284,11 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
4096 /* total space */,
"" /* no volume identifier */)
});
final Cursor cursor = mProvider.queryDocument("0_1_0", null);
final Cursor cursor = mProvider.queryDocument("1", null);
assertEquals(1, cursor.getCount());
cursor.moveToNext();
assertEquals("0_1_0", cursor.getString(0));
assertEquals("1", cursor.getString(0));
assertEquals(DocumentsContract.Document.MIME_TYPE_DIR, cursor.getString(1));
assertEquals("Device A Storage A", cursor.getString(2));
assertTrue(cursor.isNull(3));
@@ -274,42 +299,43 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
public void testQueryChildDocuments() throws Exception {
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 });
mDatabase.startAddingRootDocuments(0);
mDatabase.putRootDocuments(0, mResources, new MtpRoot[] {
new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "")
});
mDatabase.stopAddingRootDocuments(0);
setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
setupDocuments(
0,
0,
MtpManager.OBJECT_HANDLE_ROOT_CHILDREN,
"1",
new MtpObjectInfo[] {
new MtpObjectInfo.Builder()
.setObjectHandle(100)
.setFormat(MtpConstants.FORMAT_EXIF_JPEG)
.setName("image.jpg")
.setCompressedSize(1024 * 1024 * 5)
.setThumbCompressedSize(5 * 1024)
.setProtectionStatus(MtpConstants.PROTECTION_STATUS_READ_ONLY)
.build()
});
mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
.setObjectHandle(1)
.setFormat(MtpConstants.FORMAT_EXIF_JPEG)
.setName("image.jpg")
.setCompressedSize(1024 * 1024 * 5)
.setThumbCompressedSize(5 * 1024)
.setProtectionStatus(MtpConstants.PROTECTION_STATUS_READ_ONLY)
.build());
final Cursor cursor = mProvider.queryChildDocuments("0_0_0", null, null);
final Cursor cursor = mProvider.queryChildDocuments("1", null, null);
assertEquals(1, cursor.getCount());
assertTrue(cursor.moveToNext());
assertEquals("0_0_1", cursor.getString(0));
assertEquals("2", cursor.getString(0));
assertEquals("image/jpeg", cursor.getString(1));
assertEquals("image.jpg", cursor.getString(2));
assertEquals(0, cursor.getLong(3));
assertEquals(DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL, cursor.getInt(4));
assertEquals(1024 * 1024 * 5, cursor.getInt(5));
assertFalse(cursor.moveToNext());
cursor.close();
}
public void testQueryChildDocuments_cursorError() throws Exception {
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
try {
mProvider.queryChildDocuments("0_0_0", null, null);
mProvider.queryChildDocuments("1", null, null);
fail();
} catch (Throwable error) {
assertTrue(error instanceof FileNotFoundException);
@@ -319,42 +345,95 @@ public class MtpDocumentsProviderTest extends AndroidTestCase {
public void testQueryChildDocuments_documentError() throws Exception {
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
setupRoots(0, new MtpRoot[] { new MtpRoot(0, 0, "Device", "Storage", 1000, 1000, "") });
mMtpManager.setObjectHandles(0, 0, -1, new int[] { 1 });
try {
mProvider.queryChildDocuments("0_0_0", null, null);
mProvider.queryChildDocuments("1", null, null);
fail();
} catch (Throwable error) {
assertTrue(error instanceof FileNotFoundException);
}
}
public void testDeleteDocument() throws IOException {
public void testDeleteDocument() throws IOException, InterruptedException, TimeoutException {
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
.setObjectHandle(1)
.setParent(2)
.build());
mProvider.deleteDocument("0_0_1");
setupRoots(0, new MtpRoot[] {
new MtpRoot(0, 0, "Device", "Storage", 0, 0, "")
});
setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
new MtpObjectInfo.Builder()
.setName("test.txt")
.setObjectHandle(1)
.setParent(-1)
.build()
});
mProvider.deleteDocument("2");
assertEquals(1, mResolver.getChangeCount(
DocumentsContract.buildChildDocumentsUri(
MtpDocumentsProvider.AUTHORITY, "0_0_2")));
MtpDocumentsProvider.AUTHORITY, "1")));
}
public void testDeleteDocument_error() throws IOException {
public void testDeleteDocument_error()
throws IOException, InterruptedException, TimeoutException {
mMtpManager.addValidDevice(0);
mProvider.openDevice(0);
mMtpManager.setObjectInfo(0, new MtpObjectInfo.Builder()
.setObjectHandle(2)
.build());
setupRoots(0, new MtpRoot[] {
new MtpRoot(0, 0, "Device", "Storage", 0, 0, "")
});
setupDocuments(0, 0, MtpManager.OBJECT_HANDLE_ROOT_CHILDREN, "1", new MtpObjectInfo[] {
new MtpObjectInfo.Builder()
.setName("test.txt")
.setObjectHandle(1)
.setParent(-1)
.build()
});
try {
mProvider.deleteDocument("0_0_1");
mProvider.deleteDocument("3");
fail();
} catch (Throwable e) {
assertTrue(e instanceof IOException);
}
assertEquals(0, mResolver.getChangeCount(
DocumentsContract.buildChildDocumentsUri(
MtpDocumentsProvider.AUTHORITY, "0_0_2")));
MtpDocumentsProvider.AUTHORITY, "1")));
}
private String[] getStrings(Cursor cursor) {
try {
final String[] results = new String[cursor.getCount()];
for (int i = 0; cursor.moveToNext(); i++) {
results[i] = cursor.getString(0);
}
return results;
} finally {
cursor.close();
}
}
private String[] setupRoots(int deviceId, MtpRoot[] roots)
throws FileNotFoundException, InterruptedException, TimeoutException {
final int changeCount = mResolver.getChangeCount(ROOTS_URI);
mMtpManager.setRoots(deviceId, roots);
mResolver.waitForNotification(ROOTS_URI, changeCount + 1);
return getStrings(mProvider.queryRoots(strings(DocumentsContract.Root.COLUMN_ROOT_ID)));
}
private String[] setupDocuments(
int deviceId,
int storageId,
int parentHandle,
String parentDocumentId,
MtpObjectInfo[] objects) throws FileNotFoundException {
final int[] handles = new int[objects.length];
int i = 0;
for (final MtpObjectInfo info : objects) {
handles[i] = info.getObjectHandle();
mMtpManager.setObjectInfo(deviceId, info);
}
mMtpManager.setObjectHandles(deviceId, storageId, parentHandle, handles);
return getStrings(mProvider.queryChildDocuments(
parentDocumentId, strings(DocumentsContract.Document.COLUMN_DOCUMENT_ID), null));
}
}

View File

@@ -44,13 +44,13 @@ public class PipeManagerTest extends AndroidTestCase {
public void testReadDocument_basic() throws Exception {
mtpManager.setImportFileBytes(0, 1, HELLO_BYTES);
final ParcelFileDescriptor descriptor = mPipeManager.readDocument(
mtpManager, new Identifier(0, 0, 1));
mtpManager, new Identifier(0, 0, 1, null));
assertDescriptor(descriptor, HELLO_BYTES);
}
public void testReadDocument_error() throws Exception {
final ParcelFileDescriptor descriptor =
mPipeManager.readDocument(mtpManager, new Identifier(0, 0, 1));
mPipeManager.readDocument(mtpManager, new Identifier(0, 0, 1, null));
assertDescriptorError(descriptor);
}
@@ -62,7 +62,7 @@ public class PipeManagerTest extends AndroidTestCase {
// Upload testing bytes.
final ParcelFileDescriptor descriptor = mPipeManager.writeDocument(
getContext(), mtpManager, new Identifier(0, 0, 1));
getContext(), mtpManager, new Identifier(0, 0, 1, null));
final ParcelFileDescriptor.AutoCloseOutputStream outputStream =
new ParcelFileDescriptor.AutoCloseOutputStream(descriptor);
outputStream.write(HELLO_BYTES, 0, HELLO_BYTES.length);
@@ -94,13 +94,13 @@ public class PipeManagerTest extends AndroidTestCase {
public void testReadThumbnail_basic() throws Exception {
mtpManager.setThumbnail(0, 1, HELLO_BYTES);
final ParcelFileDescriptor descriptor = mPipeManager.readThumbnail(
mtpManager, new Identifier(0, 0, 1));
mtpManager, new Identifier(0, 0, 1, null));
assertDescriptor(descriptor, HELLO_BYTES);
}
public void testReadThumbnail_error() throws Exception {
final ParcelFileDescriptor descriptor =
mPipeManager.readThumbnail(mtpManager, new Identifier(0, 0, 1));
mPipeManager.readThumbnail(mtpManager, new Identifier(0, 0, 1, null));
assertDescriptorError(descriptor);
}