Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3034538c authored by Daichi Hirono's avatar Daichi Hirono Committed by Android (Google) Code Review
Browse files

Merge "Extends MtpDatabase so that it can handle child documents."

parents 000885e7 a8a3722e
Loading
Loading
Loading
Loading
+106 −82
Original line number Original line Diff line number Diff line
@@ -84,8 +84,7 @@ class MtpDatabase {
            DocumentsContract.Document.COLUMN_DOCUMENT_ID + " = ?";
            DocumentsContract.Document.COLUMN_DOCUMENT_ID + " = ?";
    private static final String SELECTION_ROOT_DOCUMENTS =
    private static final String SELECTION_ROOT_DOCUMENTS =
            COLUMN_DEVICE_ID + " = ? AND " + COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
            COLUMN_DEVICE_ID + " = ? AND " + COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
    private static final String SELECTION_ROOT_DOCUMENTS_WITH_STATE =
    private static final String SELECTION_CHILD_DOCUMENTS = COLUMN_PARENT_DOCUMENT_ID + " = ?";
            SELECTION_ROOT_DOCUMENTS + " AND " + COLUMN_ROW_STATE + " = ?";


    static class ParentNotFoundException extends Exception {}
    static class ParentNotFoundException extends Exception {}


@@ -136,7 +135,7 @@ class MtpDatabase {
    }
    }


    @VisibleForTesting
    @VisibleForTesting
    Cursor queryChildDocuments(String[] columnNames) {
    Cursor queryRootDocuments(String[] columnNames) {
        return database.query(
        return database.query(
                TABLE_DOCUMENTS,
                TABLE_DOCUMENTS,
                columnNames,
                columnNames,
@@ -159,84 +158,27 @@ class MtpDatabase {
                null);
                null);
    }
    }


    /**
     * Puts the roots into the database.
     * If the database found another unmapped document that shares the same name with the root,
     * the document may be merged into the unmapped document. In that case, the database marks the
     * root as 'mapping' and wait for {@link #resolveRootDocuments(int)} is invoked.
     * @param deviceId Device ID of roots.
     * @param resources Resources used to get localized root name.
     * @param roots Roots added to the database.
     */
    @VisibleForTesting
    @VisibleForTesting
    void putRootDocuments(int deviceId, Resources resources, MtpRoot[] roots) {
    void putRootDocuments(int deviceId, Resources resources, MtpRoot[] roots) {
        database.beginTransaction();
        final ContentValues[] valuesList = new ContentValues[roots.length];
        try {
            // Add roots to database.
            final ContentValues values = new ContentValues();
        for (int i = 0; i < roots.length; i++) {
        for (int i = 0; i < roots.length; i++) {
                getRootDocumentValues(values, resources, roots[i]);
            if (roots[i].mDeviceId != deviceId) {
                final String displayName =
                throw new IllegalArgumentException();
                        values.getAsString(DocumentsContract.Document.COLUMN_DISPLAY_NAME);
                final long numUnmapped = DatabaseUtils.queryNumEntries(
                        database,
                        TABLE_DOCUMENTS,
                        SELECTION_ROOT_DOCUMENTS_WITH_STATE + " AND " +
                        DocumentsContract.Document.COLUMN_DISPLAY_NAME + " = ?",
                        strings(deviceId, ROW_STATE_UNMAPPED, displayName));
                if (numUnmapped != 0) {
                    values.put(COLUMN_ROW_STATE, ROW_STATE_MAPPING);
            }
            }
                database.insert(TABLE_DOCUMENTS, null, values);
            valuesList[i] = new ContentValues();
            }
            getRootDocumentValues(valuesList[i], resources, roots[i]);
            database.setTransactionSuccessful();
        } finally {
            database.endTransaction();
        }
        }
        putDocuments(valuesList, SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId));
    }
    }


    @VisibleForTesting
    @VisibleForTesting
    void putDocument(int deviceId, MtpObjectInfo info) throws Exception {
    void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
        database.beginTransaction();
        final ContentValues[] valuesList = new ContentValues[documents.length];
        try {
        for (int i = 0; i < documents.length; i++) {
            final String mimeType = CursorHelper.formatTypeToMimeType(info.getFormat());
            valuesList[i] = new ContentValues();

            getChildDocumentValues(valuesList[i], deviceId, parentId, documents[i]);
            int flag = 0;
            if (info.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 (info.getThumbCompressedSize() > 0) {
                flag |= DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL;
            }

            final ContentValues values = new ContentValues();
            values.put(COLUMN_DEVICE_ID, deviceId);
            values.put(COLUMN_STORAGE_ID, info.getStorageId());
            values.put(COLUMN_OBJECT_HANDLE, info.getObjectHandle());
            // TODO: Specify correct document ID.
            values.putNull(COLUMN_PARENT_DOCUMENT_ID);
            values.put(COLUMN_ROW_STATE, ROW_STATE_MAPPED);
            values.put(Document.COLUMN_MIME_TYPE, mimeType);
            values.put(Document.COLUMN_DISPLAY_NAME, info.getName());
            values.putNull(Document.COLUMN_SUMMARY);
            values.put(
                    Document.COLUMN_LAST_MODIFIED,
                    info.getDateModified() != 0 ? info.getDateModified() : null);
            values.putNull(Document.COLUMN_ICON);
            values.put(Document.COLUMN_FLAGS, flag);
            values.put(Document.COLUMN_SIZE, info.getCompressedSize());
            if (database.insert(TABLE_DOCUMENTS, null, values) == -1) {
                throw new Exception("Failed to add document.");
            }

            database.setTransactionSuccessful();
        } finally {
            database.endTransaction();
        }
        }
        putDocuments(valuesList, SELECTION_CHILD_DOCUMENTS, parentId);
    }
    }


    /**
    /**
@@ -261,15 +203,59 @@ class MtpDatabase {
        }
        }
    }
    }


    @VisibleForTesting
    void resolveRootDocuments(int deviceId) {
        resolveDocuments(SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId));
    }

    @VisibleForTesting
    void resolveChildDocuments(String parentId) {
        resolveDocuments(SELECTION_CHILD_DOCUMENTS, parentId);
    }

    /**
     * Puts the documents into the database.
     * If the database found another unmapped document that shares the same name and parent,
     * the document may be merged into the unmapped document. In that case, the database marks the
     * root as 'mapping' and wait for {@link #resolveRootDocuments(int)} is invoked.
     * @param valuesList Values that are stored in the database.
     * @param selection SQL where closure to select rows that shares the same parent.
     * @param arg Argument for selection SQL.
     */
    private void putDocuments(ContentValues[] valuesList, String selection, String arg) {
        database.beginTransaction();
        try {
            for (final ContentValues values : valuesList) {
                final String displayName =
                        values.getAsString(DocumentsContract.Document.COLUMN_DISPLAY_NAME);
                final long numUnmapped = DatabaseUtils.queryNumEntries(
                        database,
                        TABLE_DOCUMENTS,
                        selection + " AND " +
                        COLUMN_ROW_STATE + " = ? AND " +
                        DocumentsContract.Document.COLUMN_DISPLAY_NAME + " = ?",
                        strings(arg, ROW_STATE_UNMAPPED, displayName));
                if (numUnmapped != 0) {
                    values.put(COLUMN_ROW_STATE, ROW_STATE_MAPPING);
                }
                database.insert(TABLE_DOCUMENTS, null, values);
            }

            database.setTransactionSuccessful();
        } finally {
            database.endTransaction();
        }
    }

    /**
    /**
     * Maps 'unmapped' document and 'mapping' document that don't have document but shares the same
     * Maps 'unmapped' document and 'mapping' document that don't have document but shares the same
     * name.
     * name.
     * If the database does not find corresponding 'mapping' document, it just removes 'unmapped'
     * If the database does not find corresponding 'mapping' document, it just removes 'unmapped'
     * document from the database.
     * document from the database.
     * @param deviceId Device ID of roots which the method tries to resolve.
     * @param selection Query to select rows for resolving.
     * @param arg Argument for selection SQL.
     */
     */
    @VisibleForTesting
    private void resolveDocuments(String selection, String arg) {
    void resolveRootDocuments(int deviceId) {
        database.beginTransaction();
        database.beginTransaction();
        try {
        try {
            // Get 1-to-1 mapping of unmapped document and mapping document.
            // Get 1-to-1 mapping of unmapped document and mapping document.
@@ -290,8 +276,8 @@ class MtpDatabase {
                            "group_concat(" + unmappedIdQuery + ")",
                            "group_concat(" + unmappedIdQuery + ")",
                            "group_concat(" + mappingIdQuery + ")"
                            "group_concat(" + mappingIdQuery + ")"
                    },
                    },
                    SELECTION_ROOT_DOCUMENTS,
                    selection,
                    strings(deviceId),
                    strings(arg),
                    DocumentsContract.Document.COLUMN_DISPLAY_NAME,
                    DocumentsContract.Document.COLUMN_DISPLAY_NAME,
                    "count(" + unmappedIdQuery + ") = 1 AND count(" + mappingIdQuery + ") = 1",
                    "count(" + unmappedIdQuery + ") = 1 AND count(" + mappingIdQuery + ") = 1",
                    null);
                    null);
@@ -335,8 +321,8 @@ class MtpDatabase {
            // Delete all unmapped rows that cannot be mapped.
            // Delete all unmapped rows that cannot be mapped.
            database.delete(
            database.delete(
                    TABLE_DOCUMENTS,
                    TABLE_DOCUMENTS,
                    SELECTION_ROOT_DOCUMENTS_WITH_STATE,
                    COLUMN_ROW_STATE + " = ? AND " + selection,
                    strings(deviceId, ROW_STATE_UNMAPPED));
                    strings(ROW_STATE_UNMAPPED, arg));


            // The database cannot find old document ID for the mapping rows.
            // The database cannot find old document ID for the mapping rows.
            // Turn the all mapping rows into mapped state, which means the rows become to be
            // Turn the all mapping rows into mapped state, which means the rows become to be
@@ -346,8 +332,8 @@ class MtpDatabase {
            database.update(
            database.update(
                    TABLE_DOCUMENTS,
                    TABLE_DOCUMENTS,
                    values,
                    values,
                    SELECTION_ROOT_DOCUMENTS_WITH_STATE,
                    COLUMN_ROW_STATE + " = ? AND " + selection,
                    strings(deviceId, ROW_STATE_MAPPING));
                    strings(ROW_STATE_MAPPING, arg));
            database.setTransactionSuccessful();
            database.setTransactionSuccessful();
        } finally {
        } finally {
            database.endTransaction();
            database.endTransaction();
@@ -378,6 +364,44 @@ class MtpDatabase {
                (int) Math.min(root.mMaxCapacity - root.mFreeSpace, Integer.MAX_VALUE));
                (int) Math.min(root.mMaxCapacity - root.mFreeSpace, Integer.MAX_VALUE));
    }
    }


    /**
     * Gets {@link ContentValues} for the given MTP object.
     * @param values {@link ContentValues} that receives values.
     * @param deviceId Device ID of the object.
     * @param parentId Parent document ID of the object.
     * @param info MTP object info.
     */
    private void getChildDocumentValues(
            ContentValues values, int deviceId, String parentId, MtpObjectInfo info) {
        values.clear();
        final String mimeType = CursorHelper.formatTypeToMimeType(info.getFormat());
        int flag = 0;
        if (info.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 (info.getThumbCompressedSize() > 0) {
            flag |= DocumentsContract.Document.FLAG_SUPPORTS_THUMBNAIL;
        }
        values.put(COLUMN_DEVICE_ID, deviceId);
        values.put(COLUMN_STORAGE_ID, info.getStorageId());
        values.put(COLUMN_OBJECT_HANDLE, info.getObjectHandle());
        values.put(COLUMN_PARENT_DOCUMENT_ID, parentId);
        values.put(COLUMN_ROW_STATE, ROW_STATE_MAPPED);
        values.put(Document.COLUMN_MIME_TYPE, mimeType);
        values.put(Document.COLUMN_DISPLAY_NAME, info.getName());
        values.putNull(Document.COLUMN_SUMMARY);
        values.put(
                Document.COLUMN_LAST_MODIFIED,
                info.getDateModified() != 0 ? info.getDateModified() : null);
        values.putNull(Document.COLUMN_ICON);
        values.put(Document.COLUMN_FLAGS, flag);
        values.put(Document.COLUMN_SIZE, info.getCompressedSize());
    }

    /**
    /**
     * Converts values into string array.
     * Converts values into string array.
     * @param args Values converted into string array.
     * @param args Values converted into string array.
+181 −24
Original line number Original line Diff line number Diff line
@@ -46,7 +46,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
        MtpDatabase.deleteDatabase(getContext());
        MtpDatabase.deleteDatabase(getContext());
    }
    }


    public void testPutRootDocument() throws Exception {
    public void testPutRootDocuments() throws Exception {
        final MtpDatabase database = new MtpDatabase(getContext());
        final MtpDatabase database = new MtpDatabase(getContext());
        database.putRootDocuments(0, resources, new MtpRoot[] {
        database.putRootDocuments(0, resources, new MtpRoot[] {
                new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""),
                new MtpRoot(0, 1, "Device", "Storage", 1000, 2000, ""),
@@ -54,7 +54,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
                new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 1000, 2000,"")
                new MtpRoot(0, 3, "Device", "/@#%&<>Storage", 1000, 2000,"")
        });
        });


        final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES);
        final Cursor cursor = database.queryRootDocuments(COLUMN_NAMES);
        assertEquals(3, cursor.getCount());
        assertEquals(3, cursor.getCount());


        cursor.moveToNext();
        cursor.moveToNext();
@@ -81,25 +81,34 @@ public class MtpDatabaseTest extends AndroidTestCase {
        cursor.close();
        cursor.close();
    }
    }


    public void testPutDocument() throws Exception {
    private MtpObjectInfo createDocument(int objectHandle, String name, int format, int size) {
        final MtpDatabase database = new MtpDatabase(getContext());
        final MtpObjectInfo.Builder builder = new MtpObjectInfo.Builder();
        final MtpObjectInfo.Builder builder = new MtpObjectInfo.Builder();
        builder.setObjectHandle(100);
        builder.setObjectHandle(objectHandle);
        builder.setName("test.txt");
        builder.setName(name);
        builder.setStorageId(5);
        builder.setFormat(format);
        builder.setFormat(MtpConstants.FORMAT_TEXT);
        builder.setCompressedSize(size);
        builder.setCompressedSize(1000);
        return builder.build();
        database.putDocument(0, builder.build());
    }


        final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES);
    public void testPutChildDocuments() throws Exception {
        assertEquals(1, cursor.getCount());
        final MtpDatabase database = new MtpDatabase(getContext());

        database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
                createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
                createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
        });

        final Cursor cursor = database.queryChildDocuments(COLUMN_NAMES, "parentId");
        assertEquals(3, cursor.getCount());

        cursor.moveToNext();
        cursor.moveToNext();
        assertEquals("documentId", 1, cursor.getInt(0));
        assertEquals("documentId", 1, cursor.getInt(0));
        assertEquals("deviceId", 0, cursor.getInt(1));
        assertEquals("deviceId", 0, cursor.getInt(1));
        assertEquals("storageId", 5, cursor.getInt(2));
        assertEquals("storageId", 0, cursor.getInt(2));
        assertEquals("objectHandle", 100, cursor.getInt(3));
        assertEquals("objectHandle", 100, cursor.getInt(3));
        assertEquals("mimeType", "text/plain", cursor.getString(4));
        assertEquals("mimeType", "text/plain", cursor.getString(4));
        assertEquals("displayName", "test.txt", cursor.getString(5));
        assertEquals("displayName", "note.txt", cursor.getString(5));
        assertTrue("summary", cursor.isNull(6));
        assertTrue("summary", cursor.isNull(6));
        assertTrue("lastModified", cursor.isNull(7));
        assertTrue("lastModified", cursor.isNull(7));
        assertTrue("icon", cursor.isNull(8));
        assertTrue("icon", cursor.isNull(8));
@@ -108,7 +117,43 @@ public class MtpDatabaseTest extends AndroidTestCase {
                DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
                DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
                DocumentsContract.Document.FLAG_SUPPORTS_WRITE,
                DocumentsContract.Document.FLAG_SUPPORTS_WRITE,
                cursor.getInt(9));
                cursor.getInt(9));
        assertEquals("size", 1000, cursor.getInt(10));
        assertEquals("size", 1024, cursor.getInt(10));

        cursor.moveToNext();
        assertEquals("documentId", 2, cursor.getInt(0));
        assertEquals("deviceId", 0, cursor.getInt(1));
        assertEquals("storageId", 0, cursor.getInt(2));
        assertEquals("objectHandle", 101, cursor.getInt(3));
        assertEquals("mimeType", "image/jpeg", cursor.getString(4));
        assertEquals("displayName", "image.jpg", cursor.getString(5));
        assertTrue("summary", cursor.isNull(6));
        assertTrue("lastModified", cursor.isNull(7));
        assertTrue("icon", cursor.isNull(8));
        assertEquals(
                "flag",
                DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
                DocumentsContract.Document.FLAG_SUPPORTS_WRITE,
                cursor.getInt(9));
        assertEquals("size", 2 * 1024 * 1024, cursor.getInt(10));

        cursor.moveToNext();
        assertEquals("documentId", 3, cursor.getInt(0));
        assertEquals("deviceId", 0, cursor.getInt(1));
        assertEquals("storageId", 0, cursor.getInt(2));
        assertEquals("objectHandle", 102, cursor.getInt(3));
        assertEquals("mimeType", "audio/mpeg", cursor.getString(4));
        assertEquals("displayName", "music.mp3", cursor.getString(5));
        assertTrue("summary", cursor.isNull(6));
        assertTrue("lastModified", cursor.isNull(7));
        assertTrue("icon", cursor.isNull(8));
        assertEquals(
                "flag",
                DocumentsContract.Document.FLAG_SUPPORTS_DELETE |
                DocumentsContract.Document.FLAG_SUPPORTS_WRITE,
                cursor.getInt(9));
        assertEquals("size", 3 * 1024 * 1024, cursor.getInt(10));

        cursor.close();
    }
    }


    public void testRestoreIdForRootDocuments() throws Exception {
    public void testRestoreIdForRootDocuments() throws Exception {
@@ -124,7 +169,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
        });
        });


        {
        {
            final Cursor cursor = database.queryChildDocuments(columns);
            final Cursor cursor = database.queryRootDocuments(columns);
            assertEquals(2, cursor.getCount());
            assertEquals(2, cursor.getCount());
            cursor.moveToNext();
            cursor.moveToNext();
            assertEquals("documentId", 1, cursor.getInt(0));
            assertEquals("documentId", 1, cursor.getInt(0));
@@ -140,7 +185,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
        database.clearMapping();
        database.clearMapping();


        {
        {
            final Cursor cursor = database.queryChildDocuments(columns);
            final Cursor cursor = database.queryRootDocuments(columns);
            assertEquals(2, cursor.getCount());
            assertEquals(2, cursor.getCount());
            cursor.moveToNext();
            cursor.moveToNext();
            assertEquals("documentId", 1, cursor.getInt(0));
            assertEquals("documentId", 1, cursor.getInt(0));
@@ -159,7 +204,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
        });
        });


        {
        {
            final Cursor cursor = database.queryChildDocuments(columns);
            final Cursor cursor = database.queryRootDocuments(columns);
            assertEquals(3, cursor.getCount());
            assertEquals(3, cursor.getCount());
            cursor.moveToNext();
            cursor.moveToNext();
            assertEquals("documentId", 1, cursor.getInt(0));
            assertEquals("documentId", 1, cursor.getInt(0));
@@ -179,7 +224,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
        database.resolveRootDocuments(0);
        database.resolveRootDocuments(0);


        {
        {
            final Cursor cursor = database.queryChildDocuments(columns);
            final Cursor cursor = database.queryRootDocuments(columns);
            assertEquals(2, cursor.getCount());
            assertEquals(2, cursor.getCount());
            cursor.moveToNext();
            cursor.moveToNext();
            assertEquals("documentId", 1, cursor.getInt(0));
            assertEquals("documentId", 1, cursor.getInt(0));
@@ -193,6 +238,78 @@ public class MtpDatabaseTest extends AndroidTestCase {
        }
        }
    }
    }


    public void testRestoreIdForChildDocuments() throws Exception {
        final MtpDatabase database = new MtpDatabase(getContext());
        final String[] columns = new String[] {
                DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                MtpDatabase.COLUMN_OBJECT_HANDLE,
                DocumentsContract.Document.COLUMN_DISPLAY_NAME
        };
        database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
                createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                createDocument(101, "image.jpg", MtpConstants.FORMAT_EXIF_JPEG, 2 * 1024 * 1024),
                createDocument(102, "music.mp3", MtpConstants.FORMAT_MP3, 3 * 1024 * 1024)
        });
        database.clearMapping();

        {
            final Cursor cursor = database.queryChildDocuments(columns, "parentId");
            assertEquals(3, cursor.getCount());

            cursor.moveToNext();
            assertEquals("documentId", 1, cursor.getInt(0));
            assertTrue("objectHandle", cursor.isNull(1));
            assertEquals("name", "note.txt", cursor.getString(2));

            cursor.moveToNext();
            assertEquals("documentId", 2, cursor.getInt(0));
            assertTrue("objectHandle", cursor.isNull(1));
            assertEquals("name", "image.jpg", cursor.getString(2));

            cursor.moveToNext();
            assertEquals("documentId", 3, cursor.getInt(0));
            assertTrue("objectHandle", cursor.isNull(1));
            assertEquals("name", "music.mp3", cursor.getString(2));

            cursor.close();
        }

        database.putChildDocuments(0, "parentId", new MtpObjectInfo[] {
                createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
                createDocument(203, "video.mp4", MtpConstants.FORMAT_MP4_CONTAINER, 1024),
        });

        {
            final Cursor cursor = database.queryChildDocuments(columns, "parentId");
            assertEquals(4, cursor.getCount());

            cursor.moveToPosition(3);
            assertEquals("documentId", 5, cursor.getInt(0));
            assertEquals("objectHandle", 203, cursor.getInt(1));
            assertEquals("name", "video.mp4", cursor.getString(2));

            cursor.close();
        }

        database.resolveChildDocuments("parentId");

        {
            final Cursor cursor = database.queryChildDocuments(columns, "parentId");
            assertEquals(2, cursor.getCount());

            cursor.moveToNext();
            assertEquals("documentId", 1, cursor.getInt(0));
            assertEquals("objectHandle", 200, cursor.getInt(1));
            assertEquals("name", "note.txt", cursor.getString(2));

            cursor.moveToNext();
            assertEquals("documentId", 5, cursor.getInt(0));
            assertEquals("objectHandle", 203, cursor.getInt(1));
            assertEquals("name", "video.mp4", cursor.getString(2));
            cursor.close();
        }
    }

    public void testRestoreIdForDifferentDevices() throws Exception {
    public void testRestoreIdForDifferentDevices() throws Exception {
        final MtpDatabase database = new MtpDatabase(getContext());
        final MtpDatabase database = new MtpDatabase(getContext());
        final String[] columns = new String[] {
        final String[] columns = new String[] {
@@ -208,7 +325,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
        });
        });


        {
        {
            final Cursor cursor = database.queryChildDocuments(columns);
            final Cursor cursor = database.queryRootDocuments(columns);
            assertEquals(2, cursor.getCount());
            assertEquals(2, cursor.getCount());
            cursor.moveToNext();
            cursor.moveToNext();
            assertEquals("documentId", 1, cursor.getInt(0));
            assertEquals("documentId", 1, cursor.getInt(0));
@@ -231,8 +348,9 @@ public class MtpDatabaseTest extends AndroidTestCase {
        });
        });
        database.resolveRootDocuments(0);
        database.resolveRootDocuments(0);
        database.resolveRootDocuments(1);
        database.resolveRootDocuments(1);

        {
        {
            final Cursor cursor = database.queryChildDocuments(columns);
            final Cursor cursor = database.queryRootDocuments(columns);
            assertEquals(2, cursor.getCount());
            assertEquals(2, cursor.getCount());
            cursor.moveToNext();
            cursor.moveToNext();
            assertEquals("documentId", 1, cursor.getInt(0));
            assertEquals("documentId", 1, cursor.getInt(0));
@@ -246,6 +364,45 @@ public class MtpDatabaseTest extends AndroidTestCase {
        }
        }
    }
    }


    public void testRestoreIdForDifferentParents() throws Exception {
        final MtpDatabase database = new MtpDatabase(getContext());
        final String[] columns = new String[] {
                DocumentsContract.Document.COLUMN_DOCUMENT_ID,
                MtpDatabase.COLUMN_OBJECT_HANDLE
        };
        database.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
                createDocument(100, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
        });
        database.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
                createDocument(101, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
        });
        database.clearMapping();
        database.putChildDocuments(0, "parentId1", new MtpObjectInfo[] {
                createDocument(200, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
        });
        database.putChildDocuments(0, "parentId2", new MtpObjectInfo[] {
                createDocument(201, "note.txt", MtpConstants.FORMAT_TEXT, 1024),
        });
        database.resolveChildDocuments("parentId1");

        {
            final Cursor cursor = database.queryChildDocuments(columns, "parentId1");
            assertEquals(1, cursor.getCount());
            cursor.moveToNext();
            assertEquals("documentId", 1, cursor.getInt(0));
            assertEquals("objectHandle", 200, cursor.getInt(1));
            cursor.close();
        }
        {
            final Cursor cursor = database.queryChildDocuments(columns, "parentId2");
            assertEquals(1, cursor.getCount());
            cursor.moveToNext();
            assertEquals("documentId", 2, cursor.getInt(0));
            assertTrue("objectHandle", cursor.isNull(1));
            cursor.close();
        }
    }

    public void testClearMtpIdentifierBeforeResolveRootDocuments() {
    public void testClearMtpIdentifierBeforeResolveRootDocuments() {
        final MtpDatabase database = new MtpDatabase(getContext());
        final MtpDatabase database = new MtpDatabase(getContext());
        final String[] columns = new String[] {
        final String[] columns = new String[] {
@@ -266,7 +423,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
        });
        });
        database.resolveRootDocuments(0);
        database.resolveRootDocuments(0);
        {
        {
            final Cursor cursor = database.queryChildDocuments(columns);
            final Cursor cursor = database.queryRootDocuments(columns);
            assertEquals(1, cursor.getCount());
            assertEquals(1, cursor.getCount());
            cursor.moveToNext();
            cursor.moveToNext();
            assertEquals("documentId", 1, cursor.getInt(0));
            assertEquals("documentId", 1, cursor.getInt(0));
@@ -293,7 +450,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
        });
        });
        database.resolveRootDocuments(0);
        database.resolveRootDocuments(0);
        {
        {
            final Cursor cursor = database.queryChildDocuments(columns);
            final Cursor cursor = database.queryRootDocuments(columns);
            assertEquals(2, cursor.getCount());
            assertEquals(2, cursor.getCount());
            cursor.moveToNext();
            cursor.moveToNext();
            assertEquals("documentId", 2, cursor.getInt(0));
            assertEquals("documentId", 2, cursor.getInt(0));