summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Daichi Hirono <hirono@google.com> 2015-11-19 10:19:52 +0900
committer Daichi Hirono <hirono@google.com> 2015-11-19 16:00:10 +0900
commit49f920fbde1dce6f789c2d0ad2df014c12e7e3ac (patch)
treed510f8ac2a11f4a85742ef07533cc30a0dd4be99
parente1d57710fb38dae2747aadca0e5b6f98a84a0514 (diff)
Add new methods to MtpDatabase.
* deleteDocument * getParentId * queryDocument * putNewDocument BUG=25756881 Change-Id: Ie223b37e04586b3e2b0448d09e14562fedbb652a
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/DocumentLoader.java6
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabase.java44
-rw-r--r--packages/MtpDocumentsProvider/src/com/android/mtp/MtpDatabaseInternal.java119
-rw-r--r--packages/MtpDocumentsProvider/tests/src/com/android/mtp/MtpDatabaseTest.java154
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();
}
}
}