diff options
| author | 2015-11-19 10:19:52 +0900 | |
|---|---|---|
| committer | 2015-11-19 16:00:10 +0900 | |
| commit | 49f920fbde1dce6f789c2d0ad2df014c12e7e3ac (patch) | |
| tree | d510f8ac2a11f4a85742ef07533cc30a0dd4be99 | |
| parent | e1d57710fb38dae2747aadca0e5b6f98a84a0514 (diff) | |
Add new methods to MtpDatabase.
* deleteDocument
* getParentId
* queryDocument
* putNewDocument
BUG=25756881
Change-Id: Ie223b37e04586b3e2b0448d09e14562fedbb652a
4 files changed, 308 insertions, 15 deletions
diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java index 1c96906a1d79..d4018e120f6b 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java @@ -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( diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java index 3dc69ccbf1ee..d535adc456a0 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java @@ -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; @@ -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); } @@ -145,6 +154,35 @@ class MtpDatabase { } /** + * {@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. */ @@ -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; diff --git a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java index 9c5d6b6ad3ec..580c45e17c74 100644 --- a/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java +++ b/packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java @@ -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; /** @@ -118,14 +119,87 @@ class MtpDatabaseInternal { } /** + * 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. * @return Identifier. @@ -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. @@ -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; @@ -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); @@ -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 { @@ -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(); @@ -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]); diff --git a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java index 25dd1c83766e..e568f2bb6ccc 100644 --- a/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java +++ b/packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java @@ -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[] { @@ -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); @@ -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, ""), - }); - fail(); - } catch (Throwable e) { - assertTrue(e instanceof Error); + mDatabase.putRootDocuments(0, resources, new MtpRoot[] { + 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, ""), + }); + 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(); } } } |