Loading packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java +6 −0 Original line number Diff line number Diff line Loading @@ -78,6 +78,12 @@ class DocumentLoader { if (parentHandle == CursorHelper.DUMMY_HANDLE_FOR_ROOT) { parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN; } // TODO: Handle nit race around here. // 1. getObjectHandles. // 2. putNewDocument. // 3. startAddingChildDocuemnts. // 4. stopAddingChildDocuments - It removes the new document added at the step 2, // because it is not updated between start/stopAddingChildDocuments. task = new LoaderTask(mDatabase, parent, mMtpManager.getObjectHandles( parent.mDeviceId, parent.mStorageId, parentHandle)); task.fillDocuments(loadDocuments( Loading packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java +43 −1 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.provider.DocumentsContract.Root; import com.android.internal.annotations.VisibleForTesting; import java.io.FileNotFoundException; import java.util.HashMap; import java.util.Map; Loading Loading @@ -133,6 +134,14 @@ class MtpDatabase { return mDatabase.queryChildDocuments(newColumnNames, parentDocumentId); } /** * {@link MtpDatabaseInternal#queryDocument} */ @VisibleForTesting Cursor queryDocument(String documentId, String[] projection) { return mDatabase.queryDocument(documentId, projection); } Identifier createIdentifier(String parentDocumentId) { return mDatabase.createIdentifier(parentDocumentId); } Loading @@ -144,6 +153,35 @@ class MtpDatabase { mDatabase.removeDeviceRows(deviceId); } /** * {@link MtpDatabaseInternal#getParentId} * @throws FileNotFoundException */ @VisibleForTesting String getParentId(String documentId) throws FileNotFoundException { return mDatabase.getParentId(documentId); } /** * {@link MtpDatabaseInternal#deleteDocument} */ @VisibleForTesting void deleteDocument(String documentId) { mDatabase.deleteDocument(documentId); } /** * {@link MtpDatabaseInternal#putNewDocument} * @throws FileNotFoundException */ @VisibleForTesting String putNewDocument(int deviceId, String parentDocumentId, MtpObjectInfo info) throws FileNotFoundException { final ContentValues values = new ContentValues(); getChildDocumentValues(values, deviceId, parentDocumentId, info); return mDatabase.putNewDocument(parentDocumentId, values); } /** * Invokes {@link MtpDatabaseInternal#startAddingDocuments} for root documents. * @param deviceId Device ID. Loading Loading @@ -186,7 +224,11 @@ class MtpDatabase { try { final boolean heuristic; final String mapColumn; switch (mMappingMode.get(getRootDocumentsMappingStateKey(deviceId))) { final String key = getRootDocumentsMappingStateKey(deviceId); if (!mMappingMode.containsKey(key)) { throw new IllegalStateException("startAddingRootDocuments has not been called."); } switch (mMappingMode.get(key)) { case MAP_BY_MTP_IDENTIFIER: heuristic = false; mapColumn = COLUMN_STORAGE_ID; Loading packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java +114 −5 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.database.sqlite.SQLiteQueryBuilder; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import java.io.FileNotFoundException; import java.util.Objects; /** Loading Loading @@ -117,14 +118,87 @@ class MtpDatabaseInternal { null); } /** * Queries a single document. * @param documentId * @param projection * @return Database cursor. */ public Cursor queryDocument(String documentId, String[] projection) { return mDatabase.query( TABLE_DOCUMENTS, projection, SELECTION_DOCUMENT_ID, strings(documentId), null, null, null, "1"); } /** * Remove all rows belong to a device. * @param deviceId Device ID. */ void removeDeviceRows(int deviceId) { // Call non-recursive version because it anyway deletes all rows in the devices. deleteDocumentsAndRoots(COLUMN_DEVICE_ID + "=?", strings(deviceId)); } /** * Obtains parent document ID. * @param documentId * @return parent document ID. * @throws FileNotFoundException */ String getParentId(String documentId) throws FileNotFoundException { final Cursor cursor = mDatabase.query( TABLE_DOCUMENTS, strings(COLUMN_PARENT_DOCUMENT_ID), SELECTION_DOCUMENT_ID, strings(documentId), null, null, null, "1"); try { if (cursor.moveToNext()) { return cursor.getString(0); } else { throw new FileNotFoundException("Cannot find a row having ID=" + documentId); } } finally { cursor.close(); } } /** * Deletes document and its children. * @param documentId */ void deleteDocument(String documentId) { deleteDocumentsAndRootsRecursively(SELECTION_DOCUMENT_ID, strings(documentId)); } /** * Adds new document under the parent. * The method does not affect invalidated and pending documents because we know the document is * newly added and never mapped with existing ones. * @param parentDocumentId * @param values * @return Document ID of added document. */ String putNewDocument(String parentDocumentId, ContentValues values) { mDatabase.beginTransaction(); try { final long id = mDatabase.insert(TABLE_DOCUMENTS, null, values); mDatabase.setTransactionSuccessful(); return Long.toString(id); } finally { mDatabase.endTransaction(); } } /** * Gets identifier from document ID. * @param documentId Document ID. Loading Loading @@ -197,7 +271,7 @@ class MtpDatabaseInternal { mDatabase.beginTransaction(); try { // Delete all pending rows. deleteDocumentsAndRoots( deleteDocumentsAndRootsRecursively( selection + " AND " + COLUMN_ROW_STATE + "=?", strings(arg, ROW_STATE_PENDING)); // Set all documents as invalidated. Loading Loading @@ -366,14 +440,14 @@ class MtpDatabaseInternal { } // Delete 'pending' row. deleteDocumentsAndRoots(SELECTION_DOCUMENT_ID, new String[] { pendingId }); deleteDocumentsAndRootsRecursively(SELECTION_DOCUMENT_ID, new String[] { pendingId }); } mergingCursor.close(); boolean changed = false; // Delete all invalidated rows that cannot be mapped. if (deleteDocumentsAndRoots( if (deleteDocumentsAndRootsRecursively( COLUMN_ROW_STATE + " = ? AND " + selection, strings(ROW_STATE_INVALIDATED, arg))) { changed = true; Loading Loading @@ -407,7 +481,7 @@ class MtpDatabaseInternal { void clearMapping() { mDatabase.beginTransaction(); try { deleteDocumentsAndRoots(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); Loading Loading @@ -446,6 +520,39 @@ class MtpDatabaseInternal { * @param args Arguments for selection. * @return Whether the method deletes rows. */ private boolean deleteDocumentsAndRootsRecursively(String selection, String[] args) { mDatabase.beginTransaction(); try { boolean changed = false; final Cursor cursor = mDatabase.query( TABLE_DOCUMENTS, strings(Document.COLUMN_DOCUMENT_ID), selection, args, null, null, null); try { while (cursor.moveToNext()) { if (deleteDocumentsAndRootsRecursively( COLUMN_PARENT_DOCUMENT_ID + "=?", strings(cursor.getString(0)))) { changed = true; } } } finally { cursor.close(); } if (deleteDocumentsAndRoots(selection, args)) { changed = true; } mDatabase.setTransactionSuccessful(); return changed; } finally { mDatabase.endTransaction(); } } private boolean deleteDocumentsAndRoots(String selection, String[] args) { mDatabase.beginTransaction(); try { Loading @@ -464,6 +571,8 @@ class MtpDatabaseInternal { args); deleted += mDatabase.delete(TABLE_DOCUMENTS, selection, args); mDatabase.setTransactionSuccessful(); // TODO Remove child. // TODO Remove mappingState. return deleted != 0; } finally { mDatabase.endTransaction(); Loading Loading @@ -505,7 +614,7 @@ class MtpDatabaseInternal { * @param args Values converted into string array. * @return String array. */ private static String[] strings(Object... args) { static String[] strings(Object... args) { final String[] results = new String[args.length]; for (int i = 0; i < args.length; i++) { results[i] = Objects.toString(args[i]); Loading packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java +145 −9 Original line number Diff line number Diff line Loading @@ -20,10 +20,17 @@ import android.database.Cursor; import android.mtp.MtpConstants; import android.mtp.MtpObjectInfo; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import java.io.FileNotFoundException; import java.util.Set; import java.util.TreeSet; import static com.android.mtp.MtpDatabaseInternal.strings; @SmallTest public class MtpDatabaseTest extends AndroidTestCase { private final String[] COLUMN_NAMES = new String[] { Loading Loading @@ -692,7 +699,7 @@ public class MtpDatabaseTest extends AndroidTestCase { } } public void _testFailToReplaceExisitingUnmappedRoots() { public void testFailToReplaceExisitingUnmappedRoots() { // The client code should not be able to replace rows before resolving 'unmapped' rows. // Add one. mDatabase.startAddingRootDocuments(0); Loading @@ -700,18 +707,147 @@ public class MtpDatabaseTest extends AndroidTestCase { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); mDatabase.clearMapping(); final Cursor oldCursor = mDatabase.queryRoots(strings(Root.COLUMN_ROOT_ID)); assertEquals(1, oldCursor.getCount()); // Add one. mDatabase.startAddingRootDocuments(0); mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""), new MtpRoot(0, 101, "Device", "Storage B", 1000, 1000, ""), }); // Add one more before resolving unmapped documents. try { mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""), new MtpRoot(0, 102, "Device", "Storage B", 1000, 1000, ""), }); mDatabase.stopAddingRootDocuments(0); // Because the roots shares the same name, the roots should have new IDs. final Cursor newCursor = mDatabase.queryRoots(strings(Root.COLUMN_ROOT_ID)); assertEquals(2, newCursor.getCount()); oldCursor.moveToNext(); newCursor.moveToNext(); assertFalse(oldCursor.getString(0).equals(newCursor.getString(0))); newCursor.moveToNext(); assertFalse(oldCursor.getString(0).equals(newCursor.getString(0))); oldCursor.close(); newCursor.close(); } public void testQueryDocument() { mDatabase.startAddingRootDocuments(0); mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); mDatabase.stopAddingRootDocuments(0); final Cursor cursor = mDatabase.queryDocument("1", strings(Document.COLUMN_DISPLAY_NAME)); assertEquals(1, cursor.getCount()); cursor.moveToNext(); assertEquals("Device Storage A", cursor.getString(0)); cursor.close(); } public void testGetParentId() throws FileNotFoundException { mDatabase.startAddingRootDocuments(0); mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); mDatabase.stopAddingRootDocuments(0); mDatabase.startAddingChildDocuments("1"); mDatabase.putChildDocuments( 0, "1", new MtpObjectInfo[] { createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024), }); mDatabase.stopAddingChildDocuments("1"); assertEquals("1", mDatabase.getParentId("2")); } public void testDeleteDocument() { mDatabase.startAddingRootDocuments(0); mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); fail(); } catch (Throwable e) { assertTrue(e instanceof Error); mDatabase.stopAddingRootDocuments(0); mDatabase.startAddingChildDocuments("1"); mDatabase.putChildDocuments( 0, "1", new MtpObjectInfo[] { createDocument(200, "dir", MtpConstants.FORMAT_ASSOCIATION, 1024), }); mDatabase.stopAddingChildDocuments("1"); mDatabase.startAddingChildDocuments("2"); mDatabase.putChildDocuments( 0, "2", new MtpObjectInfo[] { createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024), }); mDatabase.stopAddingChildDocuments("2"); mDatabase.deleteDocument("2"); { // Do not query deleted documents. final Cursor cursor = mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "1"); assertEquals(0, cursor.getCount()); cursor.close(); } { // Child document should be deleted also. final Cursor cursor = mDatabase.queryDocument("3", strings(Document.COLUMN_DOCUMENT_ID)); assertEquals(0, cursor.getCount()); cursor.close(); } } public void testPutNewDocument() throws FileNotFoundException { mDatabase.startAddingRootDocuments(0); mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); mDatabase.stopAddingRootDocuments(0); assertEquals( "2", mDatabase.putNewDocument( 0, "1", createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024))); { final Cursor cursor = mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "1"); assertEquals(1, cursor.getCount()); cursor.moveToNext(); assertEquals("2", cursor.getString(0)); cursor.close(); } // The new document should not be mapped with existing invalidated document. mDatabase.clearMapping(); mDatabase.startAddingChildDocuments("1"); mDatabase.putNewDocument( 0, "1", createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024)); mDatabase.stopAddingChildDocuments("1"); { final Cursor cursor = mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "1"); assertEquals(1, cursor.getCount()); cursor.moveToNext(); assertEquals("3", cursor.getString(0)); cursor.close(); } } } Loading
packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java +6 −0 Original line number Diff line number Diff line Loading @@ -78,6 +78,12 @@ class DocumentLoader { if (parentHandle == CursorHelper.DUMMY_HANDLE_FOR_ROOT) { parentHandle = MtpManager.OBJECT_HANDLE_ROOT_CHILDREN; } // TODO: Handle nit race around here. // 1. getObjectHandles. // 2. putNewDocument. // 3. startAddingChildDocuemnts. // 4. stopAddingChildDocuments - It removes the new document added at the step 2, // because it is not updated between start/stopAddingChildDocuments. task = new LoaderTask(mDatabase, parent, mMtpManager.getObjectHandles( parent.mDeviceId, parent.mStorageId, parentHandle)); task.fillDocuments(loadDocuments( Loading
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java +43 −1 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.provider.DocumentsContract.Root; import com.android.internal.annotations.VisibleForTesting; import java.io.FileNotFoundException; import java.util.HashMap; import java.util.Map; Loading Loading @@ -133,6 +134,14 @@ class MtpDatabase { return mDatabase.queryChildDocuments(newColumnNames, parentDocumentId); } /** * {@link MtpDatabaseInternal#queryDocument} */ @VisibleForTesting Cursor queryDocument(String documentId, String[] projection) { return mDatabase.queryDocument(documentId, projection); } Identifier createIdentifier(String parentDocumentId) { return mDatabase.createIdentifier(parentDocumentId); } Loading @@ -144,6 +153,35 @@ class MtpDatabase { mDatabase.removeDeviceRows(deviceId); } /** * {@link MtpDatabaseInternal#getParentId} * @throws FileNotFoundException */ @VisibleForTesting String getParentId(String documentId) throws FileNotFoundException { return mDatabase.getParentId(documentId); } /** * {@link MtpDatabaseInternal#deleteDocument} */ @VisibleForTesting void deleteDocument(String documentId) { mDatabase.deleteDocument(documentId); } /** * {@link MtpDatabaseInternal#putNewDocument} * @throws FileNotFoundException */ @VisibleForTesting String putNewDocument(int deviceId, String parentDocumentId, MtpObjectInfo info) throws FileNotFoundException { final ContentValues values = new ContentValues(); getChildDocumentValues(values, deviceId, parentDocumentId, info); return mDatabase.putNewDocument(parentDocumentId, values); } /** * Invokes {@link MtpDatabaseInternal#startAddingDocuments} for root documents. * @param deviceId Device ID. Loading Loading @@ -186,7 +224,11 @@ class MtpDatabase { try { final boolean heuristic; final String mapColumn; switch (mMappingMode.get(getRootDocumentsMappingStateKey(deviceId))) { final String key = getRootDocumentsMappingStateKey(deviceId); if (!mMappingMode.containsKey(key)) { throw new IllegalStateException("startAddingRootDocuments has not been called."); } switch (mMappingMode.get(key)) { case MAP_BY_MTP_IDENTIFIER: heuristic = false; mapColumn = COLUMN_STORAGE_ID; Loading
packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java +114 −5 Original line number Diff line number Diff line Loading @@ -29,6 +29,7 @@ import android.database.sqlite.SQLiteQueryBuilder; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import java.io.FileNotFoundException; import java.util.Objects; /** Loading Loading @@ -117,14 +118,87 @@ class MtpDatabaseInternal { null); } /** * Queries a single document. * @param documentId * @param projection * @return Database cursor. */ public Cursor queryDocument(String documentId, String[] projection) { return mDatabase.query( TABLE_DOCUMENTS, projection, SELECTION_DOCUMENT_ID, strings(documentId), null, null, null, "1"); } /** * Remove all rows belong to a device. * @param deviceId Device ID. */ void removeDeviceRows(int deviceId) { // Call non-recursive version because it anyway deletes all rows in the devices. deleteDocumentsAndRoots(COLUMN_DEVICE_ID + "=?", strings(deviceId)); } /** * Obtains parent document ID. * @param documentId * @return parent document ID. * @throws FileNotFoundException */ String getParentId(String documentId) throws FileNotFoundException { final Cursor cursor = mDatabase.query( TABLE_DOCUMENTS, strings(COLUMN_PARENT_DOCUMENT_ID), SELECTION_DOCUMENT_ID, strings(documentId), null, null, null, "1"); try { if (cursor.moveToNext()) { return cursor.getString(0); } else { throw new FileNotFoundException("Cannot find a row having ID=" + documentId); } } finally { cursor.close(); } } /** * Deletes document and its children. * @param documentId */ void deleteDocument(String documentId) { deleteDocumentsAndRootsRecursively(SELECTION_DOCUMENT_ID, strings(documentId)); } /** * Adds new document under the parent. * The method does not affect invalidated and pending documents because we know the document is * newly added and never mapped with existing ones. * @param parentDocumentId * @param values * @return Document ID of added document. */ String putNewDocument(String parentDocumentId, ContentValues values) { mDatabase.beginTransaction(); try { final long id = mDatabase.insert(TABLE_DOCUMENTS, null, values); mDatabase.setTransactionSuccessful(); return Long.toString(id); } finally { mDatabase.endTransaction(); } } /** * Gets identifier from document ID. * @param documentId Document ID. Loading Loading @@ -197,7 +271,7 @@ class MtpDatabaseInternal { mDatabase.beginTransaction(); try { // Delete all pending rows. deleteDocumentsAndRoots( deleteDocumentsAndRootsRecursively( selection + " AND " + COLUMN_ROW_STATE + "=?", strings(arg, ROW_STATE_PENDING)); // Set all documents as invalidated. Loading Loading @@ -366,14 +440,14 @@ class MtpDatabaseInternal { } // Delete 'pending' row. deleteDocumentsAndRoots(SELECTION_DOCUMENT_ID, new String[] { pendingId }); deleteDocumentsAndRootsRecursively(SELECTION_DOCUMENT_ID, new String[] { pendingId }); } mergingCursor.close(); boolean changed = false; // Delete all invalidated rows that cannot be mapped. if (deleteDocumentsAndRoots( if (deleteDocumentsAndRootsRecursively( COLUMN_ROW_STATE + " = ? AND " + selection, strings(ROW_STATE_INVALIDATED, arg))) { changed = true; Loading Loading @@ -407,7 +481,7 @@ class MtpDatabaseInternal { void clearMapping() { mDatabase.beginTransaction(); try { deleteDocumentsAndRoots(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); Loading Loading @@ -446,6 +520,39 @@ class MtpDatabaseInternal { * @param args Arguments for selection. * @return Whether the method deletes rows. */ private boolean deleteDocumentsAndRootsRecursively(String selection, String[] args) { mDatabase.beginTransaction(); try { boolean changed = false; final Cursor cursor = mDatabase.query( TABLE_DOCUMENTS, strings(Document.COLUMN_DOCUMENT_ID), selection, args, null, null, null); try { while (cursor.moveToNext()) { if (deleteDocumentsAndRootsRecursively( COLUMN_PARENT_DOCUMENT_ID + "=?", strings(cursor.getString(0)))) { changed = true; } } } finally { cursor.close(); } if (deleteDocumentsAndRoots(selection, args)) { changed = true; } mDatabase.setTransactionSuccessful(); return changed; } finally { mDatabase.endTransaction(); } } private boolean deleteDocumentsAndRoots(String selection, String[] args) { mDatabase.beginTransaction(); try { Loading @@ -464,6 +571,8 @@ class MtpDatabaseInternal { args); deleted += mDatabase.delete(TABLE_DOCUMENTS, selection, args); mDatabase.setTransactionSuccessful(); // TODO Remove child. // TODO Remove mappingState. return deleted != 0; } finally { mDatabase.endTransaction(); Loading Loading @@ -505,7 +614,7 @@ class MtpDatabaseInternal { * @param args Values converted into string array. * @return String array. */ private static String[] strings(Object... args) { static String[] strings(Object... args) { final String[] results = new String[args.length]; for (int i = 0; i < args.length; i++) { results[i] = Objects.toString(args[i]); Loading
packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java +145 −9 Original line number Diff line number Diff line Loading @@ -20,10 +20,17 @@ import android.database.Cursor; import android.mtp.MtpConstants; import android.mtp.MtpObjectInfo; import android.provider.DocumentsContract; import android.provider.DocumentsContract.Document; import android.provider.DocumentsContract.Root; import android.test.AndroidTestCase; import android.test.suitebuilder.annotation.SmallTest; import java.io.FileNotFoundException; import java.util.Set; import java.util.TreeSet; import static com.android.mtp.MtpDatabaseInternal.strings; @SmallTest public class MtpDatabaseTest extends AndroidTestCase { private final String[] COLUMN_NAMES = new String[] { Loading Loading @@ -692,7 +699,7 @@ public class MtpDatabaseTest extends AndroidTestCase { } } public void _testFailToReplaceExisitingUnmappedRoots() { public void testFailToReplaceExisitingUnmappedRoots() { // The client code should not be able to replace rows before resolving 'unmapped' rows. // Add one. mDatabase.startAddingRootDocuments(0); Loading @@ -700,18 +707,147 @@ public class MtpDatabaseTest extends AndroidTestCase { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); mDatabase.clearMapping(); final Cursor oldCursor = mDatabase.queryRoots(strings(Root.COLUMN_ROOT_ID)); assertEquals(1, oldCursor.getCount()); // Add one. mDatabase.startAddingRootDocuments(0); mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""), new MtpRoot(0, 101, "Device", "Storage B", 1000, 1000, ""), }); // Add one more before resolving unmapped documents. try { mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage B", 1000, 1000, ""), new MtpRoot(0, 102, "Device", "Storage B", 1000, 1000, ""), }); mDatabase.stopAddingRootDocuments(0); // Because the roots shares the same name, the roots should have new IDs. final Cursor newCursor = mDatabase.queryRoots(strings(Root.COLUMN_ROOT_ID)); assertEquals(2, newCursor.getCount()); oldCursor.moveToNext(); newCursor.moveToNext(); assertFalse(oldCursor.getString(0).equals(newCursor.getString(0))); newCursor.moveToNext(); assertFalse(oldCursor.getString(0).equals(newCursor.getString(0))); oldCursor.close(); newCursor.close(); } public void testQueryDocument() { mDatabase.startAddingRootDocuments(0); mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); mDatabase.stopAddingRootDocuments(0); final Cursor cursor = mDatabase.queryDocument("1", strings(Document.COLUMN_DISPLAY_NAME)); assertEquals(1, cursor.getCount()); cursor.moveToNext(); assertEquals("Device Storage A", cursor.getString(0)); cursor.close(); } public void testGetParentId() throws FileNotFoundException { mDatabase.startAddingRootDocuments(0); mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); mDatabase.stopAddingRootDocuments(0); mDatabase.startAddingChildDocuments("1"); mDatabase.putChildDocuments( 0, "1", new MtpObjectInfo[] { createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024), }); mDatabase.stopAddingChildDocuments("1"); assertEquals("1", mDatabase.getParentId("2")); } public void testDeleteDocument() { mDatabase.startAddingRootDocuments(0); mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); fail(); } catch (Throwable e) { assertTrue(e instanceof Error); mDatabase.stopAddingRootDocuments(0); mDatabase.startAddingChildDocuments("1"); mDatabase.putChildDocuments( 0, "1", new MtpObjectInfo[] { createDocument(200, "dir", MtpConstants.FORMAT_ASSOCIATION, 1024), }); mDatabase.stopAddingChildDocuments("1"); mDatabase.startAddingChildDocuments("2"); mDatabase.putChildDocuments( 0, "2", new MtpObjectInfo[] { createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024), }); mDatabase.stopAddingChildDocuments("2"); mDatabase.deleteDocument("2"); { // Do not query deleted documents. final Cursor cursor = mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "1"); assertEquals(0, cursor.getCount()); cursor.close(); } { // Child document should be deleted also. final Cursor cursor = mDatabase.queryDocument("3", strings(Document.COLUMN_DOCUMENT_ID)); assertEquals(0, cursor.getCount()); cursor.close(); } } public void testPutNewDocument() throws FileNotFoundException { mDatabase.startAddingRootDocuments(0); mDatabase.putRootDocuments(0, resources, new MtpRoot[] { new MtpRoot(0, 100, "Device", "Storage A", 0, 0, ""), }); mDatabase.stopAddingRootDocuments(0); assertEquals( "2", mDatabase.putNewDocument( 0, "1", createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024))); { final Cursor cursor = mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "1"); assertEquals(1, cursor.getCount()); cursor.moveToNext(); assertEquals("2", cursor.getString(0)); cursor.close(); } // The new document should not be mapped with existing invalidated document. mDatabase.clearMapping(); mDatabase.startAddingChildDocuments("1"); mDatabase.putNewDocument( 0, "1", createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024)); mDatabase.stopAddingChildDocuments("1"); { final Cursor cursor = mDatabase.queryChildDocuments(strings(Document.COLUMN_DOCUMENT_ID), "1"); assertEquals(1, cursor.getCount()); cursor.moveToNext(); assertEquals("3", cursor.getString(0)); cursor.close(); } } }