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

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

Merge "Add new methods to MtpDatabase."

parents 7fc7f48a 49f920fb
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -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(
+43 −1
Original line number Diff line number Diff line
@@ -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);
    }
@@ -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.
@@ -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;
+114 −5
Original line number Diff line number Diff line
@@ -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;

/**
@@ -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.
@@ -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]);
+145 −9
Original line number Diff line number Diff line
@@ -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, ""),
                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();
        }
    }
}