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

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

Merge "Integerate mapping methods for root/child documents into the same methods."

parents 5a0fed68 7a375c40
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -255,7 +255,7 @@ class DocumentLoader {
                return;
            }
            if (mNumLoaded == 0) {
                mDatabase.getMapper().startAddingChildDocuments(mIdentifier.mDocumentId);
                mDatabase.getMapper().startAddingDocuments(mIdentifier.mDocumentId);
            }
            try {
                mDatabase.getMapper().putChildDocuments(
@@ -266,7 +266,7 @@ class DocumentLoader {
                mNumLoaded = 0;
            }
            if (getState() != STATE_LOADING) {
                mDatabase.getMapper().stopAddingChildDocuments(mIdentifier.mDocumentId);
                mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId);
            }
        }

@@ -275,7 +275,7 @@ class DocumentLoader {
            mError = message;
            mNumLoaded = 0;
            if (lastState == STATE_LOADING) {
                mDatabase.getMapper().stopAddingChildDocuments(mIdentifier.mDocumentId);
                mDatabase.getMapper().stopAddingDocuments(mIdentifier.mDocumentId);
            }
        }

+66 −124
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.mtp;

import static com.android.mtp.MtpDatabaseConstants.*;

import android.annotation.Nullable;
import android.content.ContentValues;
import android.content.res.Resources;
import android.database.Cursor;
@@ -36,7 +37,6 @@ import java.util.Map;

import static com.android.mtp.MtpDatabase.strings;


/**
 * Mapping operations for MtpDatabase.
 * Also see the comments of {@link MtpDatabase}.
@@ -45,8 +45,9 @@ class Mapper {
    private final MtpDatabase mDatabase;

    /**
     * Mapping mode for roots/documents where we start adding child documents.
     * Mapping mode for a parent. The key is document ID of parent, or null for root documents.
     * Methods operate the state needs to be synchronized.
     * TODO: Replace this with unboxing int map.
     */
    private final Map<String, Integer> mMappingMode = new HashMap<>();

@@ -54,32 +55,6 @@ class Mapper {
        mDatabase = database;
    }

    /**
     * Invokes {@link #startAddingDocuments} for root documents.
     * @param deviceId Device ID.
     */
    synchronized void startAddingRootDocuments(int deviceId) {
        final String mappingStateKey = getRootDocumentsMappingStateKey(deviceId);
        Preconditions.checkState(!mMappingMode.containsKey(mappingStateKey));
        mMappingMode.put(
                mappingStateKey,
                startAddingDocuments(
                        SELECTION_ROOT_DOCUMENTS, Integer.toString(deviceId)));
    }

    /**
     * Invokes {@link #startAddingDocuments} for child of specific documents.
     * @param parentDocumentId Document ID for parent document.
     */
    @VisibleForTesting
    synchronized void startAddingChildDocuments(String parentDocumentId) {
        final String mappingStateKey = getChildDocumentsMappingStateKey(parentDocumentId);
        Preconditions.checkState(!mMappingMode.containsKey(mappingStateKey));
        mMappingMode.put(
                mappingStateKey,
                startAddingDocuments(SELECTION_CHILD_DOCUMENTS, parentDocumentId));
    }

    /**
     * Puts root information to database.
     * @param deviceId Device ID
@@ -93,9 +68,8 @@ class Mapper {
        try {
            final boolean heuristic;
            final String mapColumn;
            final String key = getRootDocumentsMappingStateKey(deviceId);
            Preconditions.checkState(mMappingMode.containsKey(key));
            switch (mMappingMode.get(key)) {
            Preconditions.checkState(mMappingMode.containsKey(/* no parent for root */ null));
            switch (mMappingMode.get(/* no parent for root */ null)) {
                case MAP_BY_MTP_IDENTIFIER:
                    heuristic = false;
                    mapColumn = COLUMN_STORAGE_ID;
@@ -117,8 +91,8 @@ class Mapper {
            }
            final boolean changed = putDocuments(
                    valuesList,
                    SELECTION_ROOT_DOCUMENTS,
                    Integer.toString(deviceId),
                    COLUMN_PARENT_DOCUMENT_ID + " IS NULL",
                    new String[0],
                    heuristic,
                    mapColumn);
            final ContentValues values = new ContentValues();
@@ -156,9 +130,8 @@ class Mapper {
    synchronized void putChildDocuments(int deviceId, String parentId, MtpObjectInfo[] documents) {
        final boolean heuristic;
        final String mapColumn;
        final String key = getChildDocumentsMappingStateKey(parentId);
        Preconditions.checkState(mMappingMode.containsKey(key));
        switch (mMappingMode.get(key)) {
        Preconditions.checkState(mMappingMode.containsKey(parentId));
        switch (mMappingMode.get(parentId)) {
            case MAP_BY_MTP_IDENTIFIER:
                heuristic = false;
                mapColumn = COLUMN_OBJECT_HANDLE;
@@ -177,59 +150,11 @@ class Mapper {
                    valuesList[i], deviceId, parentId, documents[i]);
        }
        putDocuments(
                valuesList, SELECTION_CHILD_DOCUMENTS, parentId, heuristic, mapColumn);
    }

    /**
     * Stops adding root documents.
     * @param deviceId Device ID.
     * @return True if new rows are added/removed.
     */
    synchronized boolean stopAddingRootDocuments(int deviceId) {
        final String key = getRootDocumentsMappingStateKey(deviceId);
        Preconditions.checkState(mMappingMode.containsKey(key));
        switch (mMappingMode.get(key)) {
            case MAP_BY_MTP_IDENTIFIER:
                mMappingMode.remove(key);
                return stopAddingDocuments(
                        SELECTION_ROOT_DOCUMENTS,
                        Integer.toString(deviceId),
                        COLUMN_STORAGE_ID);
            case MAP_BY_NAME:
                mMappingMode.remove(key);
                return stopAddingDocuments(
                        SELECTION_ROOT_DOCUMENTS,
                        Integer.toString(deviceId),
                        Document.COLUMN_DISPLAY_NAME);
            default:
                throw new Error("Unexpected mapping state.");
        }
    }

    /**
     * Stops adding documents under the parent.
     * @param parentId Document ID of the parent.
     */
    synchronized void stopAddingChildDocuments(String parentId) {
        final String key = getChildDocumentsMappingStateKey(parentId);
        Preconditions.checkState(mMappingMode.containsKey(key));
        switch (mMappingMode.get(key)) {
            case MAP_BY_MTP_IDENTIFIER:
                stopAddingDocuments(
                        SELECTION_CHILD_DOCUMENTS,
                        parentId,
                        COLUMN_OBJECT_HANDLE);
                break;
            case MAP_BY_NAME:
                stopAddingDocuments(
                        SELECTION_CHILD_DOCUMENTS,
                        parentId,
                        Document.COLUMN_DISPLAY_NAME);
                break;
            default:
                throw new Error("Unexpected mapping state.");
        }
        mMappingMode.remove(key);
                valuesList,
                COLUMN_PARENT_DOCUMENT_ID + "=?",
                strings(parentId),
                heuristic,
                mapColumn);
    }

    @VisibleForTesting
@@ -257,31 +182,42 @@ class Mapper {
     * identifier or not. If all the documents have MTP identifier, it uses the identifier to find
     * a corresponding existing row. Otherwise it does heuristic.
     *
     * @param selection Query matches valid documents.
     * @param arg Argument for selection.
     * @return Mapping mode.
     * @param parentDocumentId Parent document ID or NULL for root documents.
     */
    private int startAddingDocuments(String selection, String arg) {
    void startAddingDocuments(@Nullable String parentDocumentId) {
        Preconditions.checkState(!mMappingMode.containsKey(parentDocumentId));
        final String selection;
        final String[] args;
        if (parentDocumentId != null) {
            selection = COLUMN_PARENT_DOCUMENT_ID + " = ?";
            args = strings(parentDocumentId);
        } else {
            selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
            args = new String[0];
        }

        final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
        database.beginTransaction();
        try {
            // Delete all pending rows.
            mDatabase.deleteDocumentsAndRootsRecursively(
                    selection + " AND " + COLUMN_ROW_STATE + "=?", strings(arg, ROW_STATE_PENDING));
                    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);
            database.update(TABLE_DOCUMENTS, values, selection, new String[] { arg });
            database.update(TABLE_DOCUMENTS, values, selection, args);

            // If we have rows that does not have MTP identifier, do heuristic mapping by name.
            final boolean useNameForResolving = DatabaseUtils.queryNumEntries(
                    database,
                    TABLE_DOCUMENTS,
                    selection + " AND " + COLUMN_STORAGE_ID + " IS NULL",
                    new String[] { arg }) > 0;
                    args) > 0;
            database.setTransactionSuccessful();
            return useNameForResolving ? MAP_BY_NAME : MAP_BY_MTP_IDENTIFIER;
            mMappingMode.put(
                    parentDocumentId, useNameForResolving ? MAP_BY_NAME : MAP_BY_MTP_IDENTIFIER);
        } finally {
            database.endTransaction();
        }
@@ -292,19 +228,19 @@ class Mapper {
     * If the mapping mode is not heuristic, it just adds the rows to the database or updates the
     * existing rows with the new values. If the mapping mode is heuristic, it adds some new rows as
     * 'pending' state when that rows may be corresponding to existing 'invalidated' rows. Then
     * {@link #stopAddingDocuments(String, String, String)} turns the pending rows into 'valid'
     * {@link #stopAddingDocuments(String, String[], String)} turns the pending rows into 'valid'
     * rows. If the methods adds rows to database, it updates valueList with correct document ID.
     *
     * @param valuesList Values for documents to be stored in the database.
     * @param selection SQL where closure to select rows that shares the same parent.
     * @param arg Argument for selection SQL.
     * @param args Argument for selection SQL.
     * @param heuristic Whether the mapping mode is heuristic.
     * @return Whether the method adds new rows.
     */
    private boolean putDocuments(
            ContentValues[] valuesList,
            String selection,
            String arg,
            String[] args,
            boolean heuristic,
            String mappingKey) {
        final SQLiteDatabase database = mDatabase.getSQLiteDatabase();
@@ -318,7 +254,9 @@ class Mapper {
                        selection + " AND " +
                        COLUMN_ROW_STATE + "=? AND " +
                        mappingKey + "=?",
                        strings(arg, ROW_STATE_INVALIDATED, values.getAsString(mappingKey)),
                        DatabaseUtils.appendSelectionArgs(
                                args,
                                strings(ROW_STATE_INVALIDATED, values.getAsString(mappingKey))),
                        null,
                        null,
                        null,
@@ -362,12 +300,32 @@ class Mapper {
     * Maps 'pending' document and 'invalidated' document that shares the same column of groupKey.
     * If the database does not find corresponding 'invalidated' document, it just removes
     * 'invalidated' document from the database.
     * @param selection Query to select rows for resolving.
     * @param arg Argument for selection SQL.
     * @param groupKey Column name used to find corresponding rows.
     * @param parentId Parent document ID or null for root documents.
     * @return Whether the methods adds or removed visible rows.
     */
    private boolean stopAddingDocuments(String selection, String arg, String groupKey) {
    boolean stopAddingDocuments(@Nullable String parentId) {
        Preconditions.checkState(mMappingMode.containsKey(parentId));
        final String selection;
        final String[] args;
        if (parentId != null) {
            selection = COLUMN_PARENT_DOCUMENT_ID + "=?";
            args = strings(parentId);
        } else {
            selection = COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
            args = new String[0];
        }
        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 {
@@ -390,7 +348,7 @@ class Mapper {
                            "group_concat(" + pendingIdQuery + ")"
                    },
                    selection,
                    strings(arg),
                    args,
                    groupKey,
                    "count(" + invalidatedIdQuery + ") = 1 AND count(" + pendingIdQuery + ") = 1",
                    null);
@@ -439,7 +397,7 @@ class Mapper {
            // Delete all invalidated rows that cannot be mapped.
            if (mDatabase.deleteDocumentsAndRootsRecursively(
                    COLUMN_ROW_STATE + " = ? AND " + selection,
                    strings(ROW_STATE_INVALIDATED, arg))) {
                    DatabaseUtils.appendSelectionArgs(strings(ROW_STATE_INVALIDATED), args))) {
                changed = true;
            }

@@ -452,7 +410,7 @@ class Mapper {
                    TABLE_DOCUMENTS,
                    values,
                    COLUMN_ROW_STATE + " = ? AND " + selection,
                    strings(ROW_STATE_PENDING, arg)) != 0) {
                    DatabaseUtils.appendSelectionArgs(strings(ROW_STATE_PENDING), args)) != 0) {
                changed = true;
            }
            database.setTransactionSuccessful();
@@ -494,20 +452,4 @@ class Mapper {
        return "CASE WHEN " + COLUMN_ROW_STATE + " = " + Integer.toString(state) +
                " THEN " + a + " ELSE NULL END";
    }

    /**
     * @param deviceId Device ID.
     * @return Key for {@link #mMappingMode}.
     */
    private static String getRootDocumentsMappingStateKey(int deviceId) {
        return "RootDocuments/" + deviceId;
    }

    /**
     * @param parentDocumentId Document ID for the parent document.
     * @return Key for {@link #mMappingMode}.
     */
    private static String getChildDocumentsMappingStateKey(String parentDocumentId) {
        return "ChildDocuments/" + parentDocumentId;
    }
}
+4 −4
Original line number Diff line number Diff line
@@ -53,13 +53,13 @@ import java.util.Set;
 * by comparing the directory structure and object name.
 *
 * To start putting documents into the database, the client needs to call
 * {@link Mapper#startAddingChildDocuments(String)} with the parent document ID. Also it
 * needs to call {@link Mapper#stopAddingChildDocuments(String)} after putting all child
 * {@link Mapper#startAddingDocuments(String)} with the parent document ID. Also it
 * needs to call {@link Mapper#stopAddingDocuments(String)} after putting all child
 * documents to the database. (All explanations are same for root documents)
 *
 * database.getMapper().startAddingChildDocuments();
 * database.getMapper().startAddingDocuments();
 * database.getMapper().putChildDocuments();
 * database.getMapper().stopAddingChildDocuments();
 * database.getMapper().stopAddingDocuments();
 *
 * To update the existing documents, the client code can repeat to call the three methods again.
 * The newly added rows update corresponding existing rows that have same MTP identifier like
+0 −3
Original line number Diff line number Diff line
@@ -103,9 +103,6 @@ class MtpDatabaseConstants {

    static final String SELECTION_DOCUMENT_ID = Document.COLUMN_DOCUMENT_ID + " = ?";
    static final String SELECTION_ROOT_ID = Root.COLUMN_ROOT_ID + " = ?";
    static final String SELECTION_ROOT_DOCUMENTS =
            COLUMN_DEVICE_ID + " = ? AND " + COLUMN_PARENT_DOCUMENT_ID + " IS NULL";
    static final String SELECTION_CHILD_DOCUMENTS = COLUMN_PARENT_DOCUMENT_ID + " = ?";

    static final String QUERY_CREATE_DOCUMENTS =
            "CREATE TABLE " + TABLE_DOCUMENTS + " (" +
+6 −10
Original line number Diff line number Diff line
@@ -110,26 +110,22 @@ final class RootScanner {
                    return;
                }
                boolean changed = false;
                mDatabase.getMapper().startAddingDocuments(null /* parentDocumentId */);
                for (int deviceId : deviceIds) {
                    try {
                        final MtpRoot[] roots = mManager.getRoots(deviceId);
                        mDatabase.getMapper().startAddingRootDocuments(deviceId);
                        try {
                            if (mDatabase.getMapper().putRootDocuments(
                                    deviceId, mResources, roots)) {
                                changed = true;
                            }
                        } finally {
                            if (mDatabase.getMapper().stopAddingRootDocuments(deviceId)) {
                        if (mDatabase.getMapper().putRootDocuments(deviceId, mResources, roots)) {
                            changed = true;
                        }
                        }
                    } catch (IOException | SQLiteException exception) {
                        // The error may happen on the device. We would like to continue getting
                        // roots for other devices.
                        Log.e(MtpDocumentsProvider.TAG, exception.getMessage());
                    }
                }
                if (mDatabase.getMapper().stopAddingDocuments(null /* parentDocumentId */)) {
                    changed = true;
                }
                if (changed) {
                    notifyChange();
                }
Loading