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

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

Merge "Relax mapping rule to make the mapping logic simple."

parents b7289330 acdbc6e7
Loading
Loading
Loading
Loading
+1 −145
Original line number Diff line number Diff line
@@ -73,7 +73,6 @@ class Mapper {
                    extraValuesList,
                    COLUMN_PARENT_DOCUMENT_ID + " IS NULL",
                    EMPTY_ARGS,
                    /* heuristic */ false,
                    COLUMN_DEVICE_ID);
            database.setTransactionSuccessful();
            return changed;
@@ -92,16 +91,13 @@ class Mapper {
        final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
        database.beginTransaction();
        try {
            final boolean heuristic;
            final String mapColumn;
            Preconditions.checkState(mMappingMode.containsKey(parentDocumentId));
            switch (mMappingMode.get(parentDocumentId)) {
                case MAP_BY_MTP_IDENTIFIER:
                    heuristic = false;
                    mapColumn = COLUMN_STORAGE_ID;
                    break;
                case MAP_BY_NAME:
                    heuristic = true;
                    mapColumn = Document.COLUMN_DISPLAY_NAME;
                    break;
                default:
@@ -120,7 +116,6 @@ class Mapper {
                    extraValuesList,
                    COLUMN_PARENT_DOCUMENT_ID + "=?",
                    strings(parentDocumentId),
                    heuristic,
                    mapColumn);

            database.setTransactionSuccessful();
@@ -137,16 +132,13 @@ class Mapper {
     * @param documents List of document information.
     */
    synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
        final boolean heuristic;
        final String mapColumn;
        Preconditions.checkState(mMappingMode.containsKey(parentId));
        switch (mMappingMode.get(parentId)) {
            case MAP_BY_MTP_IDENTIFIER:
                heuristic = false;
                mapColumn = COLUMN_OBJECT_HANDLE;
                break;
            case MAP_BY_NAME:
                heuristic = true;
                mapColumn = Document.COLUMN_DISPLAY_NAME;
                break;
            default:
@@ -163,17 +155,13 @@ class Mapper {
                null,
                COLUMN_PARENT_DOCUMENT_ID + "=?",
                strings(parentId),
                heuristic,
                mapColumn);
    }

    @VisibleForTesting
    void clearMapping() {
        final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
        database.beginTransaction();
        try {
            mDatabase.deleteDocumentsAndRootsRecursively(
                    COLUMN_ROW_STATE + " = ?", strings(ROW_STATE_PENDING));
            final ContentValues values = new ContentValues();
            values.putNull(COLUMN_OBJECT_HANDLE);
            values.putNull(COLUMN_STORAGE_ID);
@@ -209,11 +197,6 @@ class Mapper {
        final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
        database.beginTransaction();
        try {
            // Delete all pending rows.
            mDatabase.deleteDocumentsAndRootsRecursively(
                    selection + " AND " + COLUMN_ROW_STATE + "=?",
                    DatabaseUtils.appendSelectionArgs(args, strings(ROW_STATE_PENDING)));

            // Set all documents as invalidated.
            final ContentValues values = new ContentValues();
            values.put(COLUMN_ROW_STATE, ROW_STATE_INVALIDATED);
@@ -245,7 +228,6 @@ class Mapper {
     * @param rootExtraValuesList Values for root extra to be stored in the database.
     * @param selection SQL where closure to select rows that shares the same parent.
     * @param args Argument for selection SQL.
     * @param heuristic Whether the mapping mode is heuristic.
     * @return Whether the method adds new rows.
     */
    private boolean putDocuments(
@@ -253,7 +235,6 @@ class Mapper {
            @Nullable ContentValues[] rootExtraValuesList,
            String selection,
            String[] args,
            boolean heuristic,
            String mappingKey) {
        final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
        boolean added = false;
@@ -285,7 +266,7 @@ class Mapper {
                    if (candidateCursor.getCount() == 0) {
                        rowId = database.insert(TABLE_DOCUMENTS, null, values);
                        added = true;
                    } else if (!heuristic) {
                    } else {
                        candidateCursor.moveToNext();
                        rowId = candidateCursor.getLong(0);
                        database.update(
@@ -293,9 +274,6 @@ class Mapper {
                                values,
                                SELECTION_DOCUMENT_ID,
                                strings(rowId));
                    } else {
                        values.put(COLUMN_ROW_STATE, ROW_STATE_PENDING);
                        rowId = database.insertOrThrow(TABLE_DOCUMENTS, null, values);
                    }
                    // Document ID is a primary integer key of the table. So the returned row
                    // IDs should be same with the document ID.
@@ -334,84 +312,10 @@ class Mapper {
            selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
            args = EMPTY_ARGS;
        }
        final String groupKey;
        switch (mMappingMode.get(parentId)) {
            case MAP_BY_MTP_IDENTIFIER:
                groupKey = parentId != null ? COLUMN_OBJECT_HANDLE : COLUMN_STORAGE_ID;
                break;
            case MAP_BY_NAME:
                groupKey = Document.COLUMN_DISPLAY_NAME;
                break;
            default:
                throw new Error("Unexpected mapping state.");
        }
        mMappingMode.remove(parentId);
        final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
        database.beginTransaction();
        try {
            // Get 1-to-1 mapping of invalidated document and pending document.
            final String invalidatedIdQuery = createStateFilter(
                    ROW_STATE_INVALIDATED, Document.COLUMN_DOCUMENT_ID);
            final String pendingIdQuery = createStateFilter(
                    ROW_STATE_PENDING, Document.COLUMN_DOCUMENT_ID);
            // SQL should be like:
            // SELECT group_concat(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END),
            //        group_concat(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END)
            // WHERE device_id = ? AND parent_document_id IS NULL
            // GROUP BY display_name
            // HAVING count(CASE WHEN raw_state = 1 THEN document_id ELSE NULL END) = 1 AND
            //        count(CASE WHEN raw_state = 2 THEN document_id ELSE NULL END) = 1
            final Cursor mergingCursor = database.query(
                    TABLE_DOCUMENTS,
                    new String[] {
                            "group_concat(" + invalidatedIdQuery + ")",
                            "group_concat(" + pendingIdQuery + ")"
                    },
                    selection,
                    args,
                    groupKey,
                    "count(" + invalidatedIdQuery + ") = 1 AND count(" + pendingIdQuery + ") = 1",
                    null);

            final ContentValues values = new ContentValues();
            while (mergingCursor.moveToNext()) {
                final String invalidatedId = mergingCursor.getString(0);
                final String pendingId = mergingCursor.getString(1);

                // Obtain the new values including the latest object handle from mapping row.
                getFirstRow(
                        TABLE_DOCUMENTS,
                        SELECTION_DOCUMENT_ID,
                        new String[] { pendingId },
                        values);
                values.remove(Document.COLUMN_DOCUMENT_ID);
                values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
                database.update(
                        TABLE_DOCUMENTS,
                        values,
                        SELECTION_DOCUMENT_ID,
                        new String[] { invalidatedId });

                getFirstRow(
                        TABLE_ROOT_EXTRA,
                        SELECTION_ROOT_ID,
                        new String[] { pendingId },
                        values);
                if (values.size() > 0) {
                    values.remove(Root.COLUMN_ROOT_ID);
                    database.update(
                            TABLE_ROOT_EXTRA,
                            values,
                            SELECTION_ROOT_ID,
                            new String[] { invalidatedId });
                }

                // Delete 'pending' row.
                mDatabase.deleteDocumentsAndRootsRecursively(
                        SELECTION_DOCUMENT_ID, new String[] { pendingId });
            }
            mergingCursor.close();

            boolean changed = false;

            // Delete all invalidated rows that cannot be mapped.
@@ -421,58 +325,10 @@ class Mapper {
                changed = true;
            }

            // The database cannot find old document ID for the pending rows.
            // Turn the all pending rows into valid state, which means the rows become to be
            // valid with new document ID.
            values.clear();
            values.put(COLUMN_ROW_STATE, ROW_STATE_VALID);
            if (database.update(
                    TABLE_DOCUMENTS,
                    values,
                    COLUMN_ROW_STATE + " = ? AND " + selection,
                    DatabaseUtils.appendSelectionArgs(strings(ROW_STATE_PENDING), args)) != 0) {
                changed = true;
            }
            database.setTransactionSuccessful();
            return changed;
        } finally {
            database.endTransaction();
        }
    }

    /**
     * Obtains values of the first row for the query.
     * @param values ContentValues that the values are stored to.
     * @param table Target table.
     * @param selection Query to select rows.
     * @param args Argument for query.
     */
    private void getFirstRow(String table, String selection, String[] args, ContentValues values) {
        final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
        values.clear();
        final Cursor cursor = database.query(table, null, selection, args, null, null, null, "1");
        try {
            if (cursor.getCount() == 0) {
                return;
            }
            cursor.moveToNext();
            DatabaseUtils.cursorRowToContentValues(cursor, values);
        } finally {
            cursor.close();
        }
    }

    /**
     * Gets SQL expression that represents the given value or NULL depends on the row state.
     * You must pass static constants to this methods otherwise you may be suffered from SQL
     * injections.
     * @param state Expected row state.
     * @param a SQL value.
     * @return Expression that represents a if the row state is expected one, and represents NULL
     *     otherwise.
     */
    private static String createStateFilter(int state, String a) {
        return "CASE WHEN " + COLUMN_ROW_STATE + " = " + Integer.toString(state) +
                " THEN " + a + " ELSE NULL END";
    }
}
+0 −8
Original line number Diff line number Diff line
@@ -77,14 +77,6 @@ class MtpDatabaseConstants {
     */
    static final int ROW_STATE_INVALIDATED = 1;

    /**
     * The state represents the raw has a valid object handle but it may be going to be mapped with
     * another rows invalidated. After fetching all documents under the parent, the database tries
     * to map the pending documents and the invalidated documents in order to keep old document ID
     * alive.
     */
    static final int ROW_STATE_PENDING = 2;

    /**
     * Mapping mode that uses MTP identifier to find corresponding rows.
     */
+9 −8
Original line number Diff line number Diff line
@@ -310,14 +310,14 @@ public class MtpDatabaseTest extends AndroidTestCase {
            assertEquals(3, cursor.getCount());
            cursor.moveToNext();
            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
            assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
            assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
            assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
            cursor.moveToNext();
            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
            assertTrue(isNull(cursor, COLUMN_STORAGE_ID));
            assertEquals("Storage B", getString(cursor, COLUMN_DISPLAY_NAME));
            cursor.moveToNext();
            assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
            assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
            assertEquals(202, getInt(cursor, COLUMN_STORAGE_ID));
            assertEquals("Storage C", getString(cursor, COLUMN_DISPLAY_NAME));
            cursor.close();
@@ -333,7 +333,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
            assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
            assertEquals("Storage A", getString(cursor, COLUMN_DISPLAY_NAME));
            cursor.moveToNext();
            assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
            assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
            assertEquals(202, getInt(cursor, COLUMN_STORAGE_ID));
            assertEquals("Storage C", getString(cursor, COLUMN_DISPLAY_NAME));
            cursor.close();
@@ -387,7 +387,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
            assertEquals(4, cursor.getCount());

            cursor.moveToPosition(3);
            assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
            assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
            assertEquals(203, getInt(cursor, COLUMN_OBJECT_HANDLE));
            assertEquals("video.mp4", getString(cursor, COLUMN_DISPLAY_NAME));

@@ -406,7 +406,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
            assertEquals("note.txt", getString(cursor, COLUMN_DISPLAY_NAME));

            cursor.moveToNext();
            assertEquals(5, getInt(cursor, COLUMN_DOCUMENT_ID));
            assertEquals(4, getInt(cursor, COLUMN_DOCUMENT_ID));
            assertEquals(203, getInt(cursor, COLUMN_OBJECT_HANDLE));
            assertEquals("video.mp4", getString(cursor, COLUMN_DISPLAY_NAME));
            cursor.close();
@@ -544,7 +544,7 @@ public class MtpDatabaseTest extends AndroidTestCase {
            assertEquals(1, cursor.getCount());
            cursor.moveToNext();
            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
            assertTrue(isNull(cursor, COLUMN_OBJECT_HANDLE));
            assertEquals(201, getInt(cursor, COLUMN_OBJECT_HANDLE));
            cursor.close();
        }
    }
@@ -626,11 +626,12 @@ public class MtpDatabaseTest extends AndroidTestCase {
            final Cursor cursor = mDatabase.queryRootDocuments(columns);
            assertEquals(2, cursor.getCount());
            cursor.moveToNext();
            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
            // One reuses exisitng document ID 1.
            assertEquals(1, getInt(cursor, COLUMN_DOCUMENT_ID));
            assertEquals(200, getInt(cursor, COLUMN_STORAGE_ID));
            assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
            cursor.moveToNext();
            assertEquals(3, getInt(cursor, COLUMN_DOCUMENT_ID));
            assertEquals(2, getInt(cursor, COLUMN_DOCUMENT_ID));
            assertEquals(201, getInt(cursor, COLUMN_STORAGE_ID));
            assertEquals("Storage", getString(cursor, COLUMN_DISPLAY_NAME));
            cursor.close();